Recovered state: integrated TrendSurferAgent, restored frontend/backend files, and cleaned up recovery scripts
This commit is contained in:
@@ -6,8 +6,8 @@ Normalizes persona data and onboarding information into reusable brand tokens.
|
||||
from typing import Dict, Any, Optional
|
||||
from loguru import logger
|
||||
|
||||
from services.onboarding import OnboardingDatabaseService
|
||||
from services.database import SessionLocal
|
||||
from api.content_planning.services.content_strategy.onboarding import OnboardingDataIntegrationService
|
||||
|
||||
|
||||
class BrandDNASyncService:
|
||||
@@ -16,6 +16,7 @@ class BrandDNASyncService:
|
||||
def __init__(self):
|
||||
"""Initialize Brand DNA Sync Service."""
|
||||
self.logger = logger
|
||||
self.integration_service = OnboardingDataIntegrationService()
|
||||
logger.info("[Brand DNA Sync] Service initialized")
|
||||
|
||||
def get_brand_dna_tokens(self, user_id: str) -> Dict[str, Any]:
|
||||
@@ -31,10 +32,16 @@ class BrandDNASyncService:
|
||||
try:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
onboarding_db = OnboardingDatabaseService(db)
|
||||
website_analysis = onboarding_db.get_website_analysis(user_id, db)
|
||||
persona_data = onboarding_db.get_persona_data(user_id, db)
|
||||
competitor_analyses = onboarding_db.get_competitor_analysis(user_id, db)
|
||||
# Use SSOT Integration Service
|
||||
integrated_data = self.integration_service.get_integrated_data_sync(user_id, db)
|
||||
|
||||
# Get canonical profile as primary source
|
||||
canonical_profile = integrated_data.get('canonical_profile', {})
|
||||
|
||||
# Get raw data for deep fields
|
||||
website_analysis = integrated_data.get('website_analysis', {})
|
||||
persona_data = integrated_data.get('persona_data', {})
|
||||
competitor_analyses = integrated_data.get('competitor_analysis', [])
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@@ -46,42 +53,43 @@ class BrandDNASyncService:
|
||||
"competitive_positioning": {},
|
||||
}
|
||||
|
||||
# Extract writing style from website analysis
|
||||
# Layer 1: Canonical Profile (Priority)
|
||||
brand_tokens["writing_style"] = {
|
||||
"tone": canonical_profile.get('writing_tone', 'professional'),
|
||||
"voice": canonical_profile.get('writing_voice', 'authoritative'),
|
||||
"complexity": canonical_profile.get('writing_complexity', 'intermediate'),
|
||||
"engagement_level": canonical_profile.get('writing_engagement', 'moderate'),
|
||||
}
|
||||
|
||||
target_audience_raw = canonical_profile.get('target_audience')
|
||||
if isinstance(target_audience_raw, dict):
|
||||
brand_tokens["target_audience"] = {
|
||||
"demographics": target_audience_raw.get('demographics', []),
|
||||
"industry_focus": canonical_profile.get('industry', 'general'),
|
||||
"expertise_level": target_audience_raw.get('expertise_level', 'intermediate'),
|
||||
}
|
||||
|
||||
brand_tokens["visual_identity"] = {
|
||||
"color_palette": canonical_profile.get('brand_colors', []),
|
||||
"brand_values": canonical_profile.get('brand_values', []),
|
||||
"positioning": "", # To be filled from website analysis
|
||||
}
|
||||
|
||||
# Layer 2: Raw Website Analysis (Fallback/Enrichment)
|
||||
if website_analysis:
|
||||
writing_style = website_analysis.get('writing_style') or {}
|
||||
target_audience = website_analysis.get('target_audience') or {}
|
||||
brand_analysis = website_analysis.get('brand_analysis') or {}
|
||||
style_guidelines = website_analysis.get('style_guidelines') or {}
|
||||
|
||||
# Ensure writing_style is a dict before accessing
|
||||
if isinstance(writing_style, dict):
|
||||
brand_tokens["writing_style"] = {
|
||||
"tone": writing_style.get('tone', 'professional'),
|
||||
"voice": writing_style.get('voice', 'authoritative'),
|
||||
"complexity": writing_style.get('complexity', 'intermediate'),
|
||||
"engagement_level": writing_style.get('engagement_level', 'moderate'),
|
||||
}
|
||||
# Enrich visual identity if missing
|
||||
if not brand_tokens["visual_identity"]["color_palette"] and isinstance(brand_analysis, dict):
|
||||
brand_tokens["visual_identity"]["color_palette"] = brand_analysis.get('color_palette', [])
|
||||
brand_tokens["visual_identity"]["brand_values"] = brand_analysis.get('brand_values', [])
|
||||
brand_tokens["visual_identity"]["positioning"] = brand_analysis.get('positioning', '')
|
||||
|
||||
# Ensure target_audience is a dict before accessing
|
||||
if isinstance(target_audience, dict):
|
||||
brand_tokens["target_audience"] = {
|
||||
"demographics": target_audience.get('demographics', []),
|
||||
"industry_focus": target_audience.get('industry_focus', 'general'),
|
||||
"expertise_level": target_audience.get('expertise_level', 'intermediate'),
|
||||
}
|
||||
|
||||
# Ensure brand_analysis is a dict before accessing
|
||||
if isinstance(brand_analysis, dict) and brand_analysis:
|
||||
brand_tokens["visual_identity"] = {
|
||||
"color_palette": brand_analysis.get('color_palette', []),
|
||||
"brand_values": brand_analysis.get('brand_values', []),
|
||||
"positioning": brand_analysis.get('positioning', ''),
|
||||
}
|
||||
|
||||
# Add style_guidelines if available and visual_identity exists
|
||||
# Add style_guidelines if available
|
||||
if style_guidelines and isinstance(style_guidelines, dict):
|
||||
if "visual_identity" not in brand_tokens:
|
||||
brand_tokens["visual_identity"] = {}
|
||||
brand_tokens["visual_identity"]["style_guidelines"] = style_guidelines
|
||||
|
||||
# Extract persona data
|
||||
@@ -112,21 +120,48 @@ class BrandDNASyncService:
|
||||
brand_tokens["competitive_positioning"] = {
|
||||
"differentiators": [],
|
||||
"unique_value_props": [],
|
||||
"market_position": "",
|
||||
"competitor_insights": []
|
||||
}
|
||||
|
||||
# Enrich with SSOT competitor analysis data
|
||||
for competitor in competitor_analyses[:3]: # Top 3 competitors
|
||||
if not isinstance(competitor, dict):
|
||||
continue
|
||||
|
||||
analysis_data = competitor.get('analysis_data') or {}
|
||||
if isinstance(analysis_data, dict) and analysis_data:
|
||||
# Extract insights
|
||||
competitive_insights = analysis_data.get('competitive_analysis') or {}
|
||||
if isinstance(competitive_insights, dict) and competitive_insights:
|
||||
# Differentiators
|
||||
differentiators = competitive_insights.get('differentiators', [])
|
||||
if isinstance(differentiators, list) and differentiators:
|
||||
brand_tokens["competitive_positioning"]["differentiators"].extend(
|
||||
differentiators[:2]
|
||||
)
|
||||
|
||||
# Value Props
|
||||
uvp = competitive_insights.get('unique_value_propositions', [])
|
||||
if isinstance(uvp, list) and uvp:
|
||||
brand_tokens["competitive_positioning"]["unique_value_props"].extend(
|
||||
uvp[:2]
|
||||
)
|
||||
|
||||
# Market Position (take from first valid competitor or aggregate)
|
||||
if not brand_tokens["competitive_positioning"]["market_position"]:
|
||||
brand_tokens["competitive_positioning"]["market_position"] = competitive_insights.get('market_position', '')
|
||||
|
||||
# Store simplified competitor insight
|
||||
brand_tokens["competitive_positioning"]["competitor_insights"].append({
|
||||
"name": competitor.get('competitor_url', 'Unknown'),
|
||||
"strengths": analysis_data.get('strengths', [])[:3],
|
||||
"weaknesses": analysis_data.get('weaknesses', [])[:3]
|
||||
})
|
||||
|
||||
# Deduplicate lists
|
||||
brand_tokens["competitive_positioning"]["differentiators"] = list(set(brand_tokens["competitive_positioning"]["differentiators"]))
|
||||
brand_tokens["competitive_positioning"]["unique_value_props"] = list(set(brand_tokens["competitive_positioning"]["unique_value_props"]))
|
||||
|
||||
logger.info(f"[Brand DNA Sync] Extracted brand tokens for user {user_id}")
|
||||
return brand_tokens
|
||||
|
||||
@@ -9,7 +9,7 @@ from sqlalchemy.orm import Session
|
||||
from sqlalchemy import desc
|
||||
|
||||
from models.product_marketing_models import Campaign, CampaignProposal, CampaignAsset, CampaignStatus
|
||||
from services.database import SessionLocal
|
||||
from services.database import get_session_for_user
|
||||
|
||||
|
||||
class CampaignStorageService:
|
||||
@@ -35,7 +35,7 @@ class CampaignStorageService:
|
||||
Returns:
|
||||
Saved Campaign object
|
||||
"""
|
||||
db = SessionLocal()
|
||||
db = get_session_for_user(user_id)
|
||||
try:
|
||||
campaign_id = campaign_data.get('campaign_id')
|
||||
|
||||
@@ -91,7 +91,11 @@ class CampaignStorageService:
|
||||
campaign_id: str
|
||||
) -> Optional[Campaign]:
|
||||
"""Get campaign by ID."""
|
||||
db = SessionLocal()
|
||||
db = get_session_for_user(user_id)
|
||||
if not db:
|
||||
logger.error(f"Could not create database session for user {user_id}")
|
||||
return None
|
||||
|
||||
try:
|
||||
campaign = db.query(Campaign).filter(
|
||||
Campaign.campaign_id == campaign_id,
|
||||
@@ -111,7 +115,7 @@ class CampaignStorageService:
|
||||
limit: int = 50
|
||||
) -> List[Campaign]:
|
||||
"""List campaigns for user."""
|
||||
db = SessionLocal()
|
||||
db = get_session_for_user(user_id)
|
||||
try:
|
||||
query = db.query(Campaign).filter(Campaign.user_id == user_id)
|
||||
|
||||
@@ -200,7 +204,7 @@ class CampaignStorageService:
|
||||
status: str
|
||||
) -> bool:
|
||||
"""Update campaign status."""
|
||||
db = SessionLocal()
|
||||
db = get_session_for_user(user_id)
|
||||
try:
|
||||
campaign = db.query(Campaign).filter(
|
||||
Campaign.campaign_id == campaign_id,
|
||||
|
||||
@@ -7,9 +7,9 @@ from typing import Dict, Any, Optional, List
|
||||
from loguru import logger
|
||||
import json
|
||||
|
||||
from services.onboarding.database_service import OnboardingDatabaseService
|
||||
from services.database import SessionLocal
|
||||
from services.llm_providers.main_text_generation import llm_text_gen
|
||||
from api.content_planning.services.content_strategy.onboarding import OnboardingDataIntegrationService
|
||||
from .product_marketing_templates import (
|
||||
ProductMarketingTemplates,
|
||||
TemplateCategory,
|
||||
@@ -77,6 +77,7 @@ class IntelligentPromptBuilder:
|
||||
def _parse_user_input(
|
||||
self,
|
||||
user_input: str,
|
||||
user_id: str,
|
||||
asset_type: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
@@ -138,7 +139,7 @@ Output: {"product_name": "luxury watch", "product_type": "watch", "use_case": "m
|
||||
prompt=prompt,
|
||||
system_prompt=system_prompt,
|
||||
json_struct=json_struct,
|
||||
user_id=None # No user_id needed for parsing
|
||||
user_id=user_id # Pass user_id for subscription checking
|
||||
)
|
||||
|
||||
# Parse JSON response
|
||||
@@ -185,26 +186,21 @@ Output: {"product_name": "luxury watch", "product_type": "watch", "use_case": "m
|
||||
Get all onboarding data for user.
|
||||
|
||||
Returns:
|
||||
Dictionary with website_analysis, persona_data, competitor_analyses
|
||||
Dictionary with canonical_profile only (Single Source of Truth)
|
||||
"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
onboarding_db = OnboardingDatabaseService(db)
|
||||
website_analysis = onboarding_db.get_website_analysis(user_id, db)
|
||||
persona_data = onboarding_db.get_persona_data(user_id, db)
|
||||
competitor_analyses = onboarding_db.get_competitor_analysis(user_id, db)
|
||||
|
||||
integration_service = OnboardingDataIntegrationService()
|
||||
integrated_data = integration_service.get_integrated_data_sync(user_id, db)
|
||||
canonical_profile = integrated_data.get('canonical_profile', {})
|
||||
|
||||
return {
|
||||
"website_analysis": website_analysis or {},
|
||||
"persona_data": persona_data or {},
|
||||
"competitor_analyses": competitor_analyses or [],
|
||||
"canonical_profile": canonical_profile,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"[Intelligent Prompt Builder] Error getting onboarding data: {str(e)}")
|
||||
return {
|
||||
"website_analysis": {},
|
||||
"persona_data": {},
|
||||
"competitor_analyses": [],
|
||||
"canonical_profile": {},
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
@@ -218,49 +214,49 @@ Output: {"product_name": "luxury watch", "product_type": "watch", "use_case": "m
|
||||
"""
|
||||
Infer requirements from parsed input and onboarding context.
|
||||
|
||||
Uses onboarding data to fill in missing information:
|
||||
- Platform from onboarding (if user has e-commerce setup)
|
||||
- Style from brand DNA
|
||||
- Target audience from onboarding
|
||||
Uses canonical profile for:
|
||||
- Style (aesthetic)
|
||||
- Target audience
|
||||
- Brand colors
|
||||
- Tone/Voice
|
||||
"""
|
||||
requirements = parsed_input.copy()
|
||||
|
||||
website_analysis = onboarding_data.get("website_analysis", {})
|
||||
persona_data = onboarding_data.get("persona_data", {})
|
||||
# We rely strictly on canonical_profile now
|
||||
canonical_profile = onboarding_data.get("canonical_profile", {}) or {}
|
||||
|
||||
# Infer platform from onboarding
|
||||
if not requirements.get("platform_hints"):
|
||||
# Check if user has e-commerce setup (from website analysis)
|
||||
brand_analysis = website_analysis.get("brand_analysis", {})
|
||||
# Try to infer platform from website URL or other hints
|
||||
# For now, default to e-commerce if no hints
|
||||
# This logic was: if use_case == ecommerce -> shopify.
|
||||
# We can keep this simple inference or check if industry is ecommerce.
|
||||
if requirements.get("use_case") == "ecommerce":
|
||||
requirements["platform_hints"] = ["shopify"] # Default e-commerce platform
|
||||
|
||||
# Infer style from brand DNA
|
||||
# Infer style from brand DNA (canonical)
|
||||
if not requirements.get("style_hints"):
|
||||
if brand_analysis:
|
||||
style_guidelines = brand_analysis.get("style_guidelines", {})
|
||||
aesthetic = style_guidelines.get("aesthetic", "")
|
||||
if aesthetic:
|
||||
requirements["style_hints"] = [aesthetic.lower()]
|
||||
visual_style = canonical_profile.get("visual_style", {})
|
||||
aesthetic = visual_style.get("aesthetic")
|
||||
if aesthetic:
|
||||
requirements["style_hints"] = [aesthetic.lower()]
|
||||
|
||||
# Infer target audience from onboarding
|
||||
target_audience = website_analysis.get("target_audience", {})
|
||||
# Target Audience (canonical)
|
||||
target_audience = canonical_profile.get("target_audience")
|
||||
if target_audience:
|
||||
requirements["target_audience"] = target_audience
|
||||
|
||||
# Infer brand colors
|
||||
if brand_analysis:
|
||||
color_palette = brand_analysis.get("color_palette", [])
|
||||
if color_palette:
|
||||
requirements["brand_colors"] = color_palette[:5] # Top 5 colors
|
||||
# Brand colors (canonical)
|
||||
brand_colors = canonical_profile.get("brand_colors", [])
|
||||
if brand_colors:
|
||||
requirements["brand_colors"] = brand_colors[:5] # Top 5 colors
|
||||
|
||||
# Infer writing style
|
||||
writing_style = website_analysis.get("writing_style", {})
|
||||
if writing_style:
|
||||
requirements["tone"] = writing_style.get("tone", "professional")
|
||||
requirements["voice"] = writing_style.get("voice", "authoritative")
|
||||
# Tone/Voice (canonical)
|
||||
tone = canonical_profile.get("writing_tone") or "professional"
|
||||
requirements["tone"] = tone
|
||||
|
||||
voice = canonical_profile.get("writing_voice")
|
||||
if voice:
|
||||
requirements["voice"] = voice
|
||||
|
||||
return requirements
|
||||
|
||||
@@ -423,6 +419,16 @@ Output: {"product_name": "luxury watch", "product_type": "watch", "use_case": "m
|
||||
# Brand colors from onboarding
|
||||
if requirements.get("brand_colors"):
|
||||
defaults["brand_colors"] = requirements["brand_colors"]
|
||||
|
||||
# Pass through other inferred context
|
||||
if requirements.get("tone"):
|
||||
defaults["tone"] = requirements["tone"]
|
||||
if requirements.get("voice"):
|
||||
defaults["voice"] = requirements["voice"]
|
||||
if requirements.get("target_audience"):
|
||||
defaults["target_audience"] = requirements["target_audience"]
|
||||
if requirements.get("industry"):
|
||||
defaults["industry"] = requirements["industry"]
|
||||
|
||||
# Additional context
|
||||
defaults["additional_context"] = requirements.get("additional_context", "")
|
||||
|
||||
@@ -6,8 +6,8 @@ Extracts ALL onboarding data and provides personalized defaults for forms and re
|
||||
from typing import Dict, Any, Optional, List
|
||||
from loguru import logger
|
||||
|
||||
from services.onboarding.database_service import OnboardingDatabaseService
|
||||
from services.database import SessionLocal
|
||||
from api.content_planning.services.content_strategy.onboarding import OnboardingDataIntegrationService
|
||||
|
||||
|
||||
class PersonalizationService:
|
||||
@@ -38,87 +38,37 @@ class PersonalizationService:
|
||||
"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
onboarding_db = OnboardingDatabaseService(db)
|
||||
website_analysis = onboarding_db.get_website_analysis(user_id, db)
|
||||
persona_data = onboarding_db.get_persona_data(user_id, db)
|
||||
competitor_analyses = onboarding_db.get_competitor_analysis(user_id, db)
|
||||
integration_service = OnboardingDataIntegrationService()
|
||||
integrated_data = integration_service.get_integrated_data_sync(user_id, db)
|
||||
canonical_profile = integrated_data.get('canonical_profile', {})
|
||||
|
||||
# Map strictly from Canonical Profile
|
||||
preferences = {
|
||||
"industry": None,
|
||||
"target_audience": {},
|
||||
"platform_preferences": [],
|
||||
"content_preferences": [],
|
||||
"style_preferences": {},
|
||||
"brand_colors": [],
|
||||
"industry": canonical_profile.get("industry"),
|
||||
"target_audience": canonical_profile.get("target_audience", {}),
|
||||
"platform_preferences": canonical_profile.get("platform_preferences", []),
|
||||
"content_preferences": canonical_profile.get("content_types", []),
|
||||
"style_preferences": canonical_profile.get("visual_style", {}),
|
||||
"brand_colors": canonical_profile.get("brand_colors", []),
|
||||
"recommended_templates": [],
|
||||
"recommended_channels": [],
|
||||
"writing_style": {},
|
||||
"brand_values": [],
|
||||
"writing_style": {
|
||||
"tone": canonical_profile.get("writing_tone", "professional"),
|
||||
"voice": canonical_profile.get("writing_voice", "authoritative"),
|
||||
"complexity": canonical_profile.get("writing_complexity", "intermediate"),
|
||||
"engagement_level": canonical_profile.get("writing_engagement", "moderate"),
|
||||
},
|
||||
"brand_values": canonical_profile.get("brand_values", []),
|
||||
}
|
||||
|
||||
# Extract from website_analysis
|
||||
if website_analysis:
|
||||
# Industry
|
||||
target_audience = website_analysis.get("target_audience", {})
|
||||
preferences["industry"] = target_audience.get("industry_focus")
|
||||
|
||||
# Target audience
|
||||
preferences["target_audience"] = {
|
||||
"demographics": target_audience.get("demographics", []),
|
||||
"expertise_level": target_audience.get("expertise_level", "intermediate"),
|
||||
"industry_focus": target_audience.get("industry_focus"),
|
||||
}
|
||||
|
||||
# Writing style
|
||||
writing_style = website_analysis.get("writing_style", {})
|
||||
preferences["writing_style"] = {
|
||||
"tone": writing_style.get("tone", "professional"),
|
||||
"voice": writing_style.get("voice", "authoritative"),
|
||||
"complexity": writing_style.get("complexity", "intermediate"),
|
||||
"engagement_level": writing_style.get("engagement_level", "moderate"),
|
||||
}
|
||||
|
||||
# Brand colors
|
||||
brand_analysis = website_analysis.get("brand_analysis", {})
|
||||
if brand_analysis:
|
||||
preferences["brand_colors"] = brand_analysis.get("color_palette", [])
|
||||
preferences["brand_values"] = brand_analysis.get("brand_values", [])
|
||||
|
||||
# Style preferences
|
||||
style_guidelines = website_analysis.get("style_guidelines", {})
|
||||
if style_guidelines:
|
||||
preferences["style_preferences"] = {
|
||||
"aesthetic": style_guidelines.get("aesthetic", "modern"),
|
||||
"visual_style": style_guidelines.get("visual_style", "clean"),
|
||||
}
|
||||
|
||||
# Extract from persona_data
|
||||
if persona_data:
|
||||
core_persona = persona_data.get("corePersona", {})
|
||||
platform_personas = persona_data.get("platformPersonas", {})
|
||||
selected_platforms = persona_data.get("selectedPlatforms", [])
|
||||
|
||||
# Platform preferences from selected platforms
|
||||
if selected_platforms:
|
||||
preferences["platform_preferences"] = selected_platforms
|
||||
elif platform_personas:
|
||||
# Extract platforms from platform personas
|
||||
preferences["platform_preferences"] = list(platform_personas.keys())
|
||||
|
||||
# Recommended channels based on platform personas
|
||||
if platform_personas:
|
||||
# Prioritize platforms with active personas
|
||||
preferences["recommended_channels"] = list(platform_personas.keys())[:5] # Top 5
|
||||
|
||||
# Content preferences from persona
|
||||
if core_persona:
|
||||
content_format_rules = core_persona.get("content_format_rules", {})
|
||||
if content_format_rules:
|
||||
preferred_formats = content_format_rules.get("preferred_formats", [])
|
||||
preferences["content_preferences"] = preferred_formats
|
||||
|
||||
# Infer content preferences from industry
|
||||
if preferences["industry"]:
|
||||
# Ensure target_audience structure
|
||||
if isinstance(preferences["target_audience"], dict):
|
||||
ta = preferences["target_audience"]
|
||||
if "industry_focus" not in ta and preferences["industry"]:
|
||||
ta["industry_focus"] = preferences["industry"]
|
||||
|
||||
# Infer content preferences from industry if missing (Business Rule)
|
||||
if not preferences["content_preferences"] and preferences["industry"]:
|
||||
industry_content_map = {
|
||||
"ecommerce": ["product_images", "product_videos", "lifestyle_content"],
|
||||
"saas": ["feature_highlights", "tutorials", "demo_videos"],
|
||||
|
||||
@@ -7,9 +7,7 @@ from typing import Dict, Any, Optional
|
||||
from loguru import logger
|
||||
|
||||
from services.ai_prompt_optimizer import AIPromptOptimizer
|
||||
from services.onboarding import OnboardingDataService
|
||||
from services.onboarding.database_service import OnboardingDatabaseService
|
||||
from services.persona_data_service import PersonaDataService
|
||||
from api.content_planning.services.content_strategy.onboarding import OnboardingDataIntegrationService
|
||||
from services.database import SessionLocal
|
||||
|
||||
|
||||
@@ -19,9 +17,9 @@ class ProductMarketingPromptBuilder(AIPromptOptimizer):
|
||||
def __init__(self):
|
||||
"""Initialize Product Marketing Prompt Builder."""
|
||||
super().__init__()
|
||||
self.onboarding_data_service = OnboardingDataService()
|
||||
self.onboarding_integration_service = OnboardingDataIntegrationService()
|
||||
self.logger = logger
|
||||
logger.info("[Product Marketing Prompt Builder] Initialized")
|
||||
self.logger.info("[Product Marketing Prompt Builder] Initialized")
|
||||
|
||||
def build_marketing_image_prompt(
|
||||
self,
|
||||
@@ -45,66 +43,61 @@ class ProductMarketingPromptBuilder(AIPromptOptimizer):
|
||||
Enhanced prompt with brand DNA, persona style, and marketing context
|
||||
"""
|
||||
try:
|
||||
# Get onboarding data
|
||||
# Use Canonical Profile (SSOT)
|
||||
db = SessionLocal()
|
||||
try:
|
||||
onboarding_db = OnboardingDatabaseService(db)
|
||||
website_analysis = onboarding_db.get_website_analysis(user_id, db)
|
||||
persona_data = onboarding_db.get_persona_data(user_id, db)
|
||||
competitor_analyses = onboarding_db.get_competitor_analysis(user_id, db)
|
||||
onboarding_data = self.onboarding_integration_service._build_canonical_from_db(user_id, db)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error fetching onboarding data: {e}")
|
||||
onboarding_data = {}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
canonical_profile = onboarding_data or {}
|
||||
|
||||
# Build prompt layers
|
||||
enhanced_prompt = base_prompt
|
||||
|
||||
# Layer 1: Brand DNA (from website_analysis)
|
||||
if website_analysis:
|
||||
writing_style = website_analysis.get('writing_style', {})
|
||||
target_audience = website_analysis.get('target_audience', {})
|
||||
brand_analysis = website_analysis.get('brand_analysis', {})
|
||||
style_guidelines = website_analysis.get('style_guidelines', {})
|
||||
|
||||
# Add brand tone and style
|
||||
tone = writing_style.get('tone', 'professional')
|
||||
voice = writing_style.get('voice', 'authoritative')
|
||||
brand_enhancement = f", {tone} tone, {voice} voice"
|
||||
|
||||
# Add target audience context
|
||||
# 1. Brand Voice & Tone (Canonical)
|
||||
tone = canonical_profile.get('writing_tone', 'professional')
|
||||
voice = canonical_profile.get('writing_voice', 'authoritative')
|
||||
brand_enhancement = f", {tone} tone, {voice} voice"
|
||||
enhanced_prompt += brand_enhancement
|
||||
|
||||
# 2. Target Audience (Canonical)
|
||||
target_audience = canonical_profile.get('target_audience')
|
||||
demographics = []
|
||||
|
||||
if isinstance(target_audience, dict):
|
||||
demographics = target_audience.get('demographics', [])
|
||||
if demographics:
|
||||
audience_context = f", targeting {', '.join(demographics[:2])}"
|
||||
enhanced_prompt += audience_context
|
||||
|
||||
# Add brand visual identity if available
|
||||
if brand_analysis:
|
||||
color_palette = brand_analysis.get('color_palette', [])
|
||||
if color_palette:
|
||||
colors = ', '.join(color_palette[:3])
|
||||
enhanced_prompt += f", brand colors: {colors}"
|
||||
if not demographics:
|
||||
# fallback to checking keys if demographics key is missing but dict acts as demographics
|
||||
pass
|
||||
elif isinstance(target_audience, list):
|
||||
demographics = target_audience
|
||||
elif isinstance(target_audience, str):
|
||||
demographics = [target_audience]
|
||||
|
||||
if demographics:
|
||||
audience_str = ', '.join([str(d) for d in demographics[:2]])
|
||||
enhanced_prompt += f", targeting {audience_str}"
|
||||
|
||||
# Layer 2: Persona Visual Style (from persona_data)
|
||||
if persona_data:
|
||||
core_persona = persona_data.get('corePersona', {})
|
||||
platform_personas = persona_data.get('platformPersonas', {})
|
||||
# 3. Brand Identity (Canonical)
|
||||
brand_colors = canonical_profile.get('brand_colors', [])
|
||||
if brand_colors:
|
||||
colors = ', '.join([str(c) for c in brand_colors[:3]])
|
||||
enhanced_prompt += f", brand colors: {colors}"
|
||||
|
||||
if core_persona:
|
||||
persona_name = core_persona.get('persona_name', '')
|
||||
archetype = core_persona.get('archetype', '')
|
||||
if persona_name:
|
||||
enhanced_prompt += f", {persona_name} style"
|
||||
|
||||
# Channel-specific persona adaptation
|
||||
if channel and platform_personas:
|
||||
platform_persona = platform_personas.get(channel, {})
|
||||
if platform_persona:
|
||||
visual_identity = platform_persona.get('visual_identity', {})
|
||||
if visual_identity:
|
||||
aesthetic = visual_identity.get('aesthetic_preferences', '')
|
||||
if aesthetic:
|
||||
enhanced_prompt += f", {aesthetic} aesthetic"
|
||||
visual_style = canonical_profile.get('visual_style', {})
|
||||
aesthetic = visual_style.get('aesthetic')
|
||||
if aesthetic:
|
||||
enhanced_prompt += f", {aesthetic} aesthetic"
|
||||
|
||||
# 4. Persona Style (Canonical - derived from Persona Data if available)
|
||||
# Note: Canonical profile already merges persona data into tone/voice/style.
|
||||
# If we need specific persona name, we might need to check if it's stored in canonical.
|
||||
# Currently canonical stores aggregated traits.
|
||||
|
||||
# Layer 3: Channel Optimization
|
||||
# Channel-specific optimization
|
||||
channel_enhancements = {
|
||||
'instagram': ', Instagram-optimized composition, vibrant colors, engaging visual',
|
||||
'linkedin': ', professional photography, clean composition, business-focused',
|
||||
@@ -117,7 +110,6 @@ class ProductMarketingPromptBuilder(AIPromptOptimizer):
|
||||
if channel and channel.lower() in channel_enhancements:
|
||||
enhanced_prompt += channel_enhancements[channel.lower()]
|
||||
|
||||
# Layer 4: Asset Type Specific
|
||||
asset_type_enhancements = {
|
||||
'hero_image': ', hero image style, prominent product placement, professional photography',
|
||||
'product_photo': ', product photography, clean background, detailed product showcase',
|
||||
@@ -128,11 +120,6 @@ class ProductMarketingPromptBuilder(AIPromptOptimizer):
|
||||
if asset_type in asset_type_enhancements:
|
||||
enhanced_prompt += asset_type_enhancements[asset_type]
|
||||
|
||||
# Layer 5: Competitive Differentiation
|
||||
if competitor_analyses and len(competitor_analyses) > 0:
|
||||
# Extract unique positioning from competitor analysis
|
||||
enhanced_prompt += ", unique positioning, differentiated visual style"
|
||||
|
||||
# Layer 6: Quality Descriptors
|
||||
enhanced_prompt += ", professional photography, high quality, detailed, sharp focus, natural lighting"
|
||||
|
||||
@@ -142,11 +129,11 @@ class ProductMarketingPromptBuilder(AIPromptOptimizer):
|
||||
if marketing_goal:
|
||||
enhanced_prompt += f", {marketing_goal} focused"
|
||||
|
||||
logger.info(f"[Marketing Prompt] Enhanced prompt for user {user_id}: {enhanced_prompt[:200]}...")
|
||||
self.logger.info(f"[Marketing Prompt] Enhanced prompt for user {user_id}: {enhanced_prompt[:200]}...")
|
||||
return enhanced_prompt
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[Marketing Prompt] Error building prompt: {str(e)}")
|
||||
self.logger.error(f"[Marketing Prompt] Error building prompt: {str(e)}")
|
||||
# Return base prompt with minimal enhancement if error
|
||||
return f"{base_prompt}, professional photography, high quality"
|
||||
|
||||
@@ -172,97 +159,62 @@ class ProductMarketingPromptBuilder(AIPromptOptimizer):
|
||||
Enhanced prompt with persona style, brand voice, and marketing context
|
||||
"""
|
||||
try:
|
||||
# Get onboarding data
|
||||
# Use Canonical Profile (SSOT)
|
||||
db = SessionLocal()
|
||||
try:
|
||||
onboarding_db = OnboardingDatabaseService(db)
|
||||
website_analysis = onboarding_db.get_website_analysis(user_id, db)
|
||||
persona_data = onboarding_db.get_persona_data(user_id, db)
|
||||
competitor_analyses = onboarding_db.get_competitor_analysis(user_id, db)
|
||||
onboarding_data = self.onboarding_integration_service._build_canonical_from_db(user_id, db)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error fetching onboarding data: {e}")
|
||||
onboarding_data = {}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
canonical_profile = onboarding_data or {}
|
||||
|
||||
# Build enhanced prompt
|
||||
enhanced_prompt = base_request
|
||||
|
||||
# Add persona linguistic fingerprint
|
||||
if persona_data:
|
||||
core_persona = persona_data.get('corePersona', {})
|
||||
platform_personas = persona_data.get('platformPersonas', {})
|
||||
|
||||
if core_persona:
|
||||
persona_name = core_persona.get('persona_name', '')
|
||||
linguistic_fingerprint = core_persona.get('linguistic_fingerprint', {})
|
||||
|
||||
if persona_name:
|
||||
enhanced_prompt += f"\n\nFollow {persona_name} persona style:"
|
||||
|
||||
if linguistic_fingerprint:
|
||||
sentence_metrics = linguistic_fingerprint.get('sentence_metrics', {})
|
||||
lexical_features = linguistic_fingerprint.get('lexical_features', {})
|
||||
|
||||
if sentence_metrics:
|
||||
avg_length = sentence_metrics.get('average_sentence_length_words', '')
|
||||
if avg_length:
|
||||
enhanced_prompt += f"\n- Average sentence length: {avg_length} words"
|
||||
|
||||
if lexical_features:
|
||||
go_to_words = lexical_features.get('go_to_words', [])
|
||||
avoid_words = lexical_features.get('avoid_words', [])
|
||||
vocabulary_level = lexical_features.get('vocabulary_level', '')
|
||||
|
||||
if go_to_words:
|
||||
enhanced_prompt += f"\n- Use these words: {', '.join(go_to_words[:5])}"
|
||||
if avoid_words:
|
||||
enhanced_prompt += f"\n- Avoid these words: {', '.join(avoid_words[:5])}"
|
||||
if vocabulary_level:
|
||||
enhanced_prompt += f"\n- Vocabulary level: {vocabulary_level}"
|
||||
|
||||
# Channel-specific persona adaptation
|
||||
if channel and platform_personas:
|
||||
platform_persona = platform_personas.get(channel, {})
|
||||
if platform_persona:
|
||||
content_format_rules = platform_persona.get('content_format_rules', {})
|
||||
engagement_patterns = platform_persona.get('engagement_patterns', {})
|
||||
|
||||
if content_format_rules:
|
||||
char_limit = content_format_rules.get('character_limit', '')
|
||||
hashtag_strategy = content_format_rules.get('hashtag_strategy', '')
|
||||
|
||||
if char_limit:
|
||||
enhanced_prompt += f"\n- Character limit: {char_limit}"
|
||||
if hashtag_strategy:
|
||||
enhanced_prompt += f"\n- Hashtag strategy: {hashtag_strategy}"
|
||||
# 1. Brand Voice & Tone (Canonical)
|
||||
tone = canonical_profile.get('writing_tone', 'professional')
|
||||
voice = canonical_profile.get('writing_voice', 'authoritative')
|
||||
complexity = canonical_profile.get('writing_complexity', 'intermediate')
|
||||
|
||||
# Add brand voice
|
||||
if website_analysis:
|
||||
writing_style = website_analysis.get('writing_style', {})
|
||||
target_audience = website_analysis.get('target_audience', {})
|
||||
|
||||
tone = writing_style.get('tone', 'professional')
|
||||
voice = writing_style.get('voice', 'authoritative')
|
||||
enhanced_prompt += f"\n- Brand tone: {tone}, Brand voice: {voice}"
|
||||
|
||||
enhanced_prompt += f"\n\nBrand Voice & Tone:\n- Tone: {tone}\n- Voice: {voice}\n- Complexity: {complexity}"
|
||||
|
||||
# 2. Target Audience (Canonical)
|
||||
target_audience = canonical_profile.get('target_audience')
|
||||
demographics = []
|
||||
if isinstance(target_audience, dict):
|
||||
demographics = target_audience.get('demographics', [])
|
||||
expertise_level = target_audience.get('expertise_level', 'intermediate')
|
||||
if demographics:
|
||||
enhanced_prompt += f"\n- Target audience: {', '.join(demographics[:2])}, {expertise_level} level"
|
||||
elif isinstance(target_audience, list):
|
||||
demographics = target_audience
|
||||
elif isinstance(target_audience, str):
|
||||
demographics = [target_audience]
|
||||
|
||||
if demographics:
|
||||
enhanced_prompt += f"\n- Target Audience: {', '.join([str(d) for d in demographics[:3]])}"
|
||||
|
||||
# 3. Industry (Canonical)
|
||||
business_info = canonical_profile.get('business_info', {})
|
||||
industry = business_info.get('industry')
|
||||
if industry:
|
||||
enhanced_prompt += f"\n- Industry Context: {industry}"
|
||||
|
||||
# 4. Platform Preferences / Context
|
||||
if channel:
|
||||
enhanced_prompt += f"\n- Platform: {channel}"
|
||||
# Add channel specific constraints if needed, but usually base model handles it well with just platform name
|
||||
|
||||
# Add competitive positioning
|
||||
if competitor_analyses and len(competitor_analyses) > 0:
|
||||
enhanced_prompt += "\n- Differentiate from competitors, highlight unique value propositions"
|
||||
|
||||
# Add marketing context
|
||||
# 5. Marketing Context
|
||||
if product_context:
|
||||
marketing_goal = product_context.get('marketing_goal', '')
|
||||
if marketing_goal:
|
||||
enhanced_prompt += f"\n- Marketing goal: {marketing_goal}"
|
||||
enhanced_prompt += f"\n- Goal: {marketing_goal}"
|
||||
|
||||
logger.info(f"[Marketing Copy Prompt] Enhanced for user {user_id}: {enhanced_prompt[:200]}...")
|
||||
self.logger.info(f"[Marketing Copy Prompt] Enhanced for user {user_id}: {enhanced_prompt[:200]}...")
|
||||
return enhanced_prompt
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[Marketing Copy Prompt] Error building prompt: {str(e)}")
|
||||
self.logger.error(f"[Marketing Copy Prompt] Error building prompt: {str(e)}")
|
||||
return base_request
|
||||
|
||||
def optimize_marketing_prompt(
|
||||
|
||||
Reference in New Issue
Block a user