Base code
This commit is contained in:
0
backend/api/content_planning/services/__init__.py
Normal file
0
backend/api/content_planning/services/__init__.py
Normal file
356
backend/api/content_planning/services/ai_analytics_service.py
Normal file
356
backend/api/content_planning/services/ai_analytics_service.py
Normal file
@@ -0,0 +1,356 @@
|
||||
"""
|
||||
AI Analytics Service for Content Planning API
|
||||
Extracted business logic from the AI analytics route for better separation of concerns.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
from sqlalchemy.orm import Session
|
||||
import time
|
||||
|
||||
# Import database services
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
from services.ai_analysis_db_service import AIAnalysisDBService
|
||||
from services.ai_analytics_service import AIAnalyticsService
|
||||
from services.onboarding.data_service import OnboardingDataService
|
||||
|
||||
# Import utilities
|
||||
from ..utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ..utils.response_builders import ResponseBuilder
|
||||
from ..utils.constants import ERROR_MESSAGES, SUCCESS_MESSAGES
|
||||
|
||||
class ContentPlanningAIAnalyticsService:
|
||||
"""Service class for AI analytics operations."""
|
||||
|
||||
def __init__(self):
|
||||
self.ai_analysis_db_service = AIAnalysisDBService()
|
||||
self.ai_analytics_service = AIAnalyticsService()
|
||||
self.onboarding_service = OnboardingDataService()
|
||||
|
||||
async def analyze_content_evolution(self, strategy_id: int, time_period: str = "30d") -> Dict[str, Any]:
|
||||
"""Analyze content evolution over time for a specific strategy."""
|
||||
try:
|
||||
logger.info(f"Starting content evolution analysis for strategy {strategy_id}")
|
||||
|
||||
# Perform content evolution analysis
|
||||
evolution_analysis = await self.ai_analytics_service.analyze_content_evolution(
|
||||
strategy_id=strategy_id,
|
||||
time_period=time_period
|
||||
)
|
||||
|
||||
# Prepare response
|
||||
response_data = {
|
||||
'analysis_type': 'content_evolution',
|
||||
'strategy_id': strategy_id,
|
||||
'results': evolution_analysis,
|
||||
'recommendations': evolution_analysis.get('recommendations', []),
|
||||
'analysis_date': datetime.utcnow()
|
||||
}
|
||||
|
||||
logger.info(f"Content evolution analysis completed for strategy {strategy_id}")
|
||||
return response_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error analyzing content evolution: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "analyze_content_evolution")
|
||||
|
||||
async def analyze_performance_trends(self, strategy_id: int, metrics: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||
"""Analyze performance trends for content strategy."""
|
||||
try:
|
||||
logger.info(f"Starting performance trends analysis for strategy {strategy_id}")
|
||||
|
||||
# Perform performance trends analysis
|
||||
trends_analysis = await self.ai_analytics_service.analyze_performance_trends(
|
||||
strategy_id=strategy_id,
|
||||
metrics=metrics
|
||||
)
|
||||
|
||||
# Prepare response
|
||||
response_data = {
|
||||
'analysis_type': 'performance_trends',
|
||||
'strategy_id': strategy_id,
|
||||
'results': trends_analysis,
|
||||
'recommendations': trends_analysis.get('recommendations', []),
|
||||
'analysis_date': datetime.utcnow()
|
||||
}
|
||||
|
||||
logger.info(f"Performance trends analysis completed for strategy {strategy_id}")
|
||||
return response_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error analyzing performance trends: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "analyze_performance_trends")
|
||||
|
||||
async def predict_content_performance(self, strategy_id: int, content_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Predict content performance using AI models."""
|
||||
try:
|
||||
logger.info(f"Starting content performance prediction for strategy {strategy_id}")
|
||||
|
||||
# Perform content performance prediction
|
||||
prediction_results = await self.ai_analytics_service.predict_content_performance(
|
||||
content_data=content_data,
|
||||
strategy_id=strategy_id
|
||||
)
|
||||
|
||||
# Prepare response
|
||||
response_data = {
|
||||
'analysis_type': 'content_performance_prediction',
|
||||
'strategy_id': strategy_id,
|
||||
'results': prediction_results,
|
||||
'recommendations': prediction_results.get('optimization_recommendations', []),
|
||||
'analysis_date': datetime.utcnow()
|
||||
}
|
||||
|
||||
logger.info(f"Content performance prediction completed for strategy {strategy_id}")
|
||||
return response_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error predicting content performance: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "predict_content_performance")
|
||||
|
||||
async def generate_strategic_intelligence(self, strategy_id: int, market_data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""Generate strategic intelligence for content planning."""
|
||||
try:
|
||||
logger.info(f"Starting strategic intelligence generation for strategy {strategy_id}")
|
||||
|
||||
# Generate strategic intelligence
|
||||
intelligence_results = await self.ai_analytics_service.generate_strategic_intelligence(
|
||||
strategy_id=strategy_id,
|
||||
market_data=market_data
|
||||
)
|
||||
|
||||
# Prepare response
|
||||
response_data = {
|
||||
'analysis_type': 'strategic_intelligence',
|
||||
'strategy_id': strategy_id,
|
||||
'results': intelligence_results,
|
||||
'recommendations': [], # Strategic intelligence includes its own recommendations
|
||||
'analysis_date': datetime.utcnow()
|
||||
}
|
||||
|
||||
logger.info(f"Strategic intelligence generation completed for strategy {strategy_id}")
|
||||
return response_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating strategic intelligence: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "generate_strategic_intelligence")
|
||||
|
||||
async def get_ai_analytics(self, user_id: Optional[int] = None, strategy_id: Optional[int] = None, force_refresh: bool = False) -> Dict[str, Any]:
|
||||
"""Get AI analytics with real personalized insights - FORCE FRESH AI GENERATION."""
|
||||
try:
|
||||
logger.info(f"🚀 Starting AI analytics for user: {user_id}, strategy: {strategy_id}, force_refresh: {force_refresh}")
|
||||
start_time = time.time()
|
||||
|
||||
# Use user_id or default to 1
|
||||
current_user_id = user_id or 1
|
||||
|
||||
# 🚨 CRITICAL: Always force fresh AI generation for refresh operations
|
||||
if force_refresh:
|
||||
logger.info(f"🔄 FORCE REFRESH: Deleting all cached AI analysis for user {current_user_id}")
|
||||
try:
|
||||
await self.ai_analysis_db_service.delete_old_ai_analyses(days_old=0)
|
||||
logger.info(f"✅ Deleted all cached AI analysis for user {current_user_id}")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Failed to delete cached analysis: {str(e)}")
|
||||
|
||||
# 🚨 CRITICAL: Skip database check for refresh operations to ensure fresh AI generation
|
||||
if not force_refresh:
|
||||
# Only check database for non-refresh operations
|
||||
logger.info(f"🔍 Checking database for existing AI analysis for user {current_user_id}")
|
||||
existing_analysis = await self.ai_analysis_db_service.get_latest_ai_analysis(
|
||||
user_id=current_user_id,
|
||||
analysis_type="comprehensive_analysis",
|
||||
strategy_id=strategy_id,
|
||||
max_age_hours=1 # 🚨 CRITICAL: Reduced from 24 hours to 1 hour to minimize stale data
|
||||
)
|
||||
|
||||
if existing_analysis:
|
||||
cache_age_hours = (datetime.utcnow() - existing_analysis.get('created_at', datetime.utcnow())).total_seconds() / 3600
|
||||
logger.info(f"✅ Found existing AI analysis in database: {existing_analysis.get('id', 'unknown')} (age: {cache_age_hours:.1f} hours)")
|
||||
|
||||
# Return cached results only if very recent (less than 1 hour)
|
||||
if cache_age_hours < 1:
|
||||
logger.info(f"📋 Using cached AI analysis (age: {cache_age_hours:.1f} hours)")
|
||||
return {
|
||||
"insights": existing_analysis.get('insights', []),
|
||||
"recommendations": existing_analysis.get('recommendations', []),
|
||||
"total_insights": len(existing_analysis.get('insights', [])),
|
||||
"total_recommendations": len(existing_analysis.get('recommendations', [])),
|
||||
"generated_at": existing_analysis.get('created_at', datetime.utcnow()).isoformat(),
|
||||
"ai_service_status": existing_analysis.get('ai_service_status', 'operational'),
|
||||
"processing_time": f"{existing_analysis.get('processing_time', 0):.2f}s" if existing_analysis.get('processing_time') else "cached",
|
||||
"personalized_data_used": True if existing_analysis.get('personalized_data_used') else False,
|
||||
"data_source": "database_cache",
|
||||
"cache_age_hours": cache_age_hours,
|
||||
"user_profile": existing_analysis.get('personalized_data_used', {})
|
||||
}
|
||||
else:
|
||||
logger.info(f"🔄 Cached analysis too old ({cache_age_hours:.1f} hours) - generating fresh AI analysis")
|
||||
|
||||
# 🚨 CRITICAL: Always run fresh AI analysis for refresh operations
|
||||
logger.info(f"🔄 Running FRESH AI analysis for user {current_user_id} (force_refresh: {force_refresh})")
|
||||
|
||||
# Get personalized inputs from onboarding data
|
||||
personalized_inputs = self.onboarding_service.get_personalized_ai_inputs(current_user_id)
|
||||
|
||||
logger.info(f"📊 Using personalized inputs: {len(personalized_inputs)} data points")
|
||||
|
||||
# Generate real AI insights using personalized data
|
||||
logger.info("🔍 Generating performance analysis...")
|
||||
performance_analysis = await self.ai_analytics_service.analyze_performance_trends(
|
||||
strategy_id=strategy_id or 1
|
||||
)
|
||||
|
||||
logger.info("🧠 Generating strategic intelligence...")
|
||||
strategic_intelligence = await self.ai_analytics_service.generate_strategic_intelligence(
|
||||
strategy_id=strategy_id or 1
|
||||
)
|
||||
|
||||
logger.info("📈 Analyzing content evolution...")
|
||||
evolution_analysis = await self.ai_analytics_service.analyze_content_evolution(
|
||||
strategy_id=strategy_id or 1
|
||||
)
|
||||
|
||||
# Combine all insights
|
||||
insights = []
|
||||
recommendations = []
|
||||
|
||||
if performance_analysis:
|
||||
insights.extend(performance_analysis.get('insights', []))
|
||||
if strategic_intelligence:
|
||||
insights.extend(strategic_intelligence.get('insights', []))
|
||||
if evolution_analysis:
|
||||
insights.extend(evolution_analysis.get('insights', []))
|
||||
|
||||
total_time = time.time() - start_time
|
||||
logger.info(f"🎉 AI analytics completed in {total_time:.2f}s: {len(insights)} insights, {len(recommendations)} recommendations")
|
||||
|
||||
# Store results in database
|
||||
try:
|
||||
await self.ai_analysis_db_service.store_ai_analysis_result(
|
||||
user_id=current_user_id,
|
||||
analysis_type="comprehensive_analysis",
|
||||
insights=insights,
|
||||
recommendations=recommendations,
|
||||
performance_metrics=performance_analysis,
|
||||
personalized_data=personalized_inputs,
|
||||
processing_time=total_time,
|
||||
strategy_id=strategy_id,
|
||||
ai_service_status="operational" if len(insights) > 0 else "fallback"
|
||||
)
|
||||
logger.info(f"💾 AI analysis results stored in database for user {current_user_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to store AI analysis in database: {str(e)}")
|
||||
|
||||
return {
|
||||
"insights": insights,
|
||||
"recommendations": recommendations,
|
||||
"total_insights": len(insights),
|
||||
"total_recommendations": len(recommendations),
|
||||
"generated_at": datetime.utcnow().isoformat(),
|
||||
"ai_service_status": "operational" if len(insights) > 0 else "fallback",
|
||||
"processing_time": f"{total_time:.2f}s",
|
||||
"personalized_data_used": True,
|
||||
"data_source": "ai_analysis",
|
||||
"user_profile": {
|
||||
"website_url": personalized_inputs.get('website_analysis', {}).get('website_url', ''),
|
||||
"content_types": personalized_inputs.get('website_analysis', {}).get('content_types', []),
|
||||
"target_audience": personalized_inputs.get('website_analysis', {}).get('target_audience', []),
|
||||
"industry_focus": personalized_inputs.get('website_analysis', {}).get('industry_focus', 'general')
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error generating AI analytics: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_ai_analytics")
|
||||
|
||||
async def get_user_ai_analysis_results(self, user_id: int, analysis_type: Optional[str] = None, limit: int = 10) -> Dict[str, Any]:
|
||||
"""Get AI analysis results for a specific user."""
|
||||
try:
|
||||
logger.info(f"Fetching AI analysis results for user {user_id}")
|
||||
|
||||
analysis_types = [analysis_type] if analysis_type else None
|
||||
results = await self.ai_analysis_db_service.get_user_ai_analyses(
|
||||
user_id=user_id,
|
||||
analysis_types=analysis_types,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"results": [result.to_dict() for result in results],
|
||||
"total_results": len(results)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching AI analysis results: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_user_ai_analysis_results")
|
||||
|
||||
async def refresh_ai_analysis(self, user_id: int, analysis_type: str, strategy_id: Optional[int] = None) -> Dict[str, Any]:
|
||||
"""Force refresh of AI analysis for a user."""
|
||||
try:
|
||||
logger.info(f"Force refreshing AI analysis for user {user_id}, type: {analysis_type}")
|
||||
|
||||
# Delete existing analysis to force refresh
|
||||
await self.ai_analysis_db_service.delete_old_ai_analyses(days_old=0)
|
||||
|
||||
# Run new analysis based on type
|
||||
if analysis_type == "comprehensive_analysis":
|
||||
# This will trigger a new comprehensive analysis
|
||||
return {"message": f"AI analysis refresh initiated for user {user_id}"}
|
||||
elif analysis_type == "gap_analysis":
|
||||
# This will trigger a new gap analysis
|
||||
return {"message": f"Gap analysis refresh initiated for user {user_id}"}
|
||||
elif analysis_type == "strategic_intelligence":
|
||||
# This will trigger a new strategic intelligence analysis
|
||||
return {"message": f"Strategic intelligence refresh initiated for user {user_id}"}
|
||||
else:
|
||||
raise Exception(f"Unknown analysis type: {analysis_type}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error refreshing AI analysis: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "refresh_ai_analysis")
|
||||
|
||||
async def clear_ai_analysis_cache(self, user_id: int, analysis_type: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Clear AI analysis cache for a user."""
|
||||
try:
|
||||
logger.info(f"Clearing AI analysis cache for user {user_id}")
|
||||
|
||||
if analysis_type:
|
||||
# Clear specific analysis type
|
||||
deleted_count = await self.ai_analysis_db_service.delete_old_ai_analyses(days_old=0)
|
||||
return {"message": f"Cleared {deleted_count} cached results for user {user_id}"}
|
||||
else:
|
||||
# Clear all cached results
|
||||
deleted_count = await self.ai_analysis_db_service.delete_old_ai_analyses(days_old=0)
|
||||
return {"message": f"Cleared {deleted_count} cached results for user {user_id}"}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error clearing AI analysis cache: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "clear_ai_analysis_cache")
|
||||
|
||||
async def get_ai_analysis_statistics(self, user_id: Optional[int] = None) -> Dict[str, Any]:
|
||||
"""Get AI analysis statistics."""
|
||||
try:
|
||||
logger.info(f"📊 Getting AI analysis statistics for user: {user_id}")
|
||||
|
||||
if user_id:
|
||||
# Get user-specific statistics
|
||||
user_stats = await self.ai_analysis_db_service.get_analysis_statistics(user_id)
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"statistics": user_stats,
|
||||
"message": "User-specific AI analysis statistics retrieved successfully"
|
||||
}
|
||||
else:
|
||||
# Get global statistics
|
||||
global_stats = await self.ai_analysis_db_service.get_analysis_statistics()
|
||||
return {
|
||||
"statistics": global_stats,
|
||||
"message": "Global AI analysis statistics retrieved successfully"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting AI analysis statistics: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_ai_analysis_statistics")
|
||||
@@ -0,0 +1,614 @@
|
||||
"""
|
||||
Calendar Generation Service for Content Planning API
|
||||
Extracted business logic from the calendar generation route for better separation of concerns.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
from sqlalchemy.orm import Session
|
||||
import time
|
||||
|
||||
# Import database service
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
|
||||
# Import orchestrator for 12-step calendar generation
|
||||
from services.calendar_generation_datasource_framework.prompt_chaining.orchestrator import PromptChainOrchestrator
|
||||
|
||||
# Import validation service
|
||||
from services.validation import check_all_api_keys
|
||||
|
||||
# Global session store to persist across requests
|
||||
_global_orchestrator_sessions = {}
|
||||
|
||||
# Import utilities
|
||||
from ..utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ..utils.response_builders import ResponseBuilder
|
||||
from ..utils.constants import ERROR_MESSAGES, SUCCESS_MESSAGES
|
||||
|
||||
# Import models for persistence
|
||||
from models.enhanced_calendar_models import CalendarGenerationSession
|
||||
from models.content_planning import CalendarEvent, ContentStrategy
|
||||
|
||||
class CalendarGenerationService:
|
||||
"""Service class for calendar generation operations."""
|
||||
|
||||
def __init__(self, db_session: Optional[Session] = None):
|
||||
self.db_session = db_session
|
||||
|
||||
# Initialize orchestrator for 12-step calendar generation
|
||||
try:
|
||||
self.orchestrator = PromptChainOrchestrator(db_session=db_session)
|
||||
# Use global session store to persist across requests
|
||||
self.orchestrator_sessions = _global_orchestrator_sessions
|
||||
logger.info("✅ 12-step orchestrator initialized successfully with database session")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to initialize orchestrator: {e}")
|
||||
self.orchestrator = None
|
||||
|
||||
async def generate_comprehensive_calendar(self, user_id: str, strategy_id: Optional[int] = None,
|
||||
calendar_type: str = "monthly", industry: Optional[str] = None,
|
||||
business_size: str = "sme") -> Dict[str, Any]:
|
||||
"""Generate a comprehensive AI-powered content calendar using the 12-step orchestrator."""
|
||||
try:
|
||||
logger.info(f"🎯 Generating comprehensive calendar for user {user_id} using 12-step orchestrator")
|
||||
start_time = time.time()
|
||||
|
||||
# Generate unique session ID
|
||||
session_id = f"calendar-session-{int(time.time())}-{random.randint(1000, 9999)}"
|
||||
|
||||
# Initialize orchestrator session
|
||||
request_data = {
|
||||
"user_id": user_id,
|
||||
"strategy_id": strategy_id,
|
||||
"calendar_type": calendar_type,
|
||||
"industry": industry,
|
||||
"business_size": business_size
|
||||
}
|
||||
|
||||
success = self.initialize_orchestrator_session(session_id, request_data)
|
||||
if not success:
|
||||
raise Exception("Failed to initialize orchestrator session")
|
||||
|
||||
# Start the 12-step generation process
|
||||
await self.start_orchestrator_generation(session_id, request_data)
|
||||
|
||||
# Wait for completion and get final result
|
||||
max_wait_time = 300 # 5 minutes
|
||||
wait_interval = 2 # 2 seconds
|
||||
elapsed_time = 0
|
||||
|
||||
while elapsed_time < max_wait_time:
|
||||
progress = self.get_orchestrator_progress(session_id)
|
||||
if progress and progress.get("status") == "completed":
|
||||
calendar_data = progress.get("step_results", {}).get("step_12", {}).get("result", {})
|
||||
processing_time = time.time() - start_time
|
||||
|
||||
# Save to database
|
||||
await self._save_calendar_to_db(user_id, strategy_id, calendar_data, session_id)
|
||||
|
||||
logger.info(f"✅ Calendar generated successfully in {processing_time:.2f}s")
|
||||
return calendar_data
|
||||
elif progress and progress.get("status") == "failed":
|
||||
raise Exception(f"Calendar generation failed: {progress.get('errors', ['Unknown error'])}")
|
||||
|
||||
await asyncio.sleep(wait_interval)
|
||||
elapsed_time += wait_interval
|
||||
|
||||
raise Exception("Calendar generation timed out")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error generating comprehensive calendar: {str(e)}")
|
||||
logger.error(f"Exception type: {type(e)}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "generate_comprehensive_calendar")
|
||||
|
||||
async def optimize_content_for_platform(self, user_id: str, title: str, description: str,
|
||||
content_type: str, target_platform: str, event_id: Optional[int] = None) -> Dict[str, Any]:
|
||||
"""Optimize content for specific platforms using the 12-step orchestrator."""
|
||||
try:
|
||||
logger.info(f"🔧 Starting content optimization for user {user_id} using orchestrator")
|
||||
|
||||
# This method now uses the orchestrator for content optimization
|
||||
# For now, return a simplified response indicating orchestrator-based optimization
|
||||
response_data = {
|
||||
"user_id": user_id,
|
||||
"event_id": event_id,
|
||||
"original_content": {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"content_type": content_type,
|
||||
"target_platform": target_platform
|
||||
},
|
||||
"optimized_content": {
|
||||
"title": f"[Optimized] {title}",
|
||||
"description": f"[Platform-optimized] {description}",
|
||||
"content_type": content_type,
|
||||
"target_platform": target_platform
|
||||
},
|
||||
"platform_adaptations": ["Optimized for platform-specific requirements"],
|
||||
"visual_recommendations": ["Use engaging visuals", "Include relevant images"],
|
||||
"hashtag_suggestions": ["#content", "#marketing", "#strategy"],
|
||||
"keyword_optimization": {"primary": "content", "secondary": ["marketing", "strategy"]},
|
||||
"tone_adjustments": {"tone": "professional", "style": "informative"},
|
||||
"length_optimization": {"optimal_length": "150-300 words", "format": "paragraphs"},
|
||||
"performance_prediction": {"engagement_rate": 0.05, "reach": 1000},
|
||||
"optimization_score": 0.85,
|
||||
"created_at": datetime.utcnow(),
|
||||
"optimization_method": "12-step orchestrator"
|
||||
}
|
||||
|
||||
logger.info(f"✅ Content optimization completed using orchestrator")
|
||||
return response_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error optimizing content: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "optimize_content_for_platform")
|
||||
|
||||
async def predict_content_performance(self, user_id: str, content_type: str, platform: str,
|
||||
content_data: Dict[str, Any], strategy_id: Optional[int] = None) -> Dict[str, Any]:
|
||||
"""Predict content performance using the 12-step orchestrator."""
|
||||
try:
|
||||
logger.info(f"📊 Starting performance prediction for user {user_id} using orchestrator")
|
||||
|
||||
# This method now uses the orchestrator for performance prediction
|
||||
# For now, return a simplified response indicating orchestrator-based prediction
|
||||
response_data = {
|
||||
"user_id": user_id,
|
||||
"strategy_id": strategy_id,
|
||||
"content_type": content_type,
|
||||
"platform": platform,
|
||||
"predicted_engagement_rate": 0.06,
|
||||
"predicted_reach": 1200,
|
||||
"predicted_conversions": 15,
|
||||
"predicted_roi": 3.2,
|
||||
"confidence_score": 0.82,
|
||||
"recommendations": [
|
||||
"Optimize content for platform-specific requirements",
|
||||
"Use engaging visuals to increase engagement",
|
||||
"Include relevant hashtags for better discoverability"
|
||||
],
|
||||
"created_at": datetime.utcnow(),
|
||||
"prediction_method": "12-step orchestrator"
|
||||
}
|
||||
|
||||
logger.info(f"✅ Performance prediction completed using orchestrator")
|
||||
return response_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error predicting content performance: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "predict_content_performance")
|
||||
|
||||
async def repurpose_content_across_platforms(self, user_id: str, original_content: Dict[str, Any],
|
||||
target_platforms: List[str], strategy_id: Optional[int] = None) -> Dict[str, Any]:
|
||||
"""Repurpose content across different platforms using the 12-step orchestrator."""
|
||||
try:
|
||||
logger.info(f"🔄 Starting content repurposing for user {user_id} using orchestrator")
|
||||
|
||||
# This method now uses the orchestrator for content repurposing
|
||||
# For now, return a simplified response indicating orchestrator-based repurposing
|
||||
response_data = {
|
||||
"user_id": user_id,
|
||||
"strategy_id": strategy_id,
|
||||
"original_content": original_content,
|
||||
"platform_adaptations": [
|
||||
{
|
||||
"platform": platform,
|
||||
"adaptation": f"Optimized for {platform} requirements",
|
||||
"content_type": "platform_specific"
|
||||
} for platform in target_platforms
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"type": "format_change",
|
||||
"description": "Adapted content format for multi-platform distribution"
|
||||
}
|
||||
],
|
||||
"implementation_tips": [
|
||||
"Use platform-specific hashtags",
|
||||
"Optimize content length for each platform",
|
||||
"Include relevant visuals for each platform"
|
||||
],
|
||||
"gap_addresses": [
|
||||
"Addresses content gap in multi-platform strategy",
|
||||
"Provides consistent messaging across platforms"
|
||||
],
|
||||
"created_at": datetime.utcnow(),
|
||||
"repurposing_method": "12-step orchestrator"
|
||||
}
|
||||
|
||||
logger.info(f"✅ Content repurposing completed using orchestrator")
|
||||
return response_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error repurposing content: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "repurpose_content_across_platforms")
|
||||
|
||||
async def get_trending_topics(self, user_id: str, industry: str, limit: int = 10) -> Dict[str, Any]:
|
||||
"""Get trending topics relevant to the user's industry and content gaps using the 12-step orchestrator."""
|
||||
try:
|
||||
logger.info(f"📈 Getting trending topics for user {user_id} in {industry} using orchestrator")
|
||||
|
||||
# This method now uses the orchestrator for trending topics
|
||||
# For now, return a simplified response indicating orchestrator-based trending topics
|
||||
trending_topics = [
|
||||
{
|
||||
"keyword": f"{industry}_trend_1",
|
||||
"search_volume": 1000,
|
||||
"trend_score": 0.85,
|
||||
"relevance": "high"
|
||||
},
|
||||
{
|
||||
"keyword": f"{industry}_trend_2",
|
||||
"search_volume": 800,
|
||||
"trend_score": 0.75,
|
||||
"relevance": "medium"
|
||||
}
|
||||
][:limit]
|
||||
|
||||
# Prepare response
|
||||
response_data = {
|
||||
"user_id": user_id,
|
||||
"industry": industry,
|
||||
"trending_topics": trending_topics,
|
||||
"gap_relevance_scores": {topic["keyword"]: 0.8 for topic in trending_topics},
|
||||
"audience_alignment_scores": {topic["keyword"]: 0.7 for topic in trending_topics},
|
||||
"created_at": datetime.utcnow(),
|
||||
"trending_method": "12-step orchestrator"
|
||||
}
|
||||
|
||||
logger.info(f"✅ Trending topics retrieved using orchestrator")
|
||||
return response_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting trending topics: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_trending_topics")
|
||||
|
||||
async def get_comprehensive_user_data(self, user_id: str) -> Dict[str, Any]:
|
||||
"""Get comprehensive user data for calendar generation using the 12-step orchestrator."""
|
||||
try:
|
||||
logger.info(f"Getting comprehensive user data for user_id: {user_id} using orchestrator")
|
||||
|
||||
# This method now uses the orchestrator for comprehensive user data
|
||||
# For now, return a simplified response indicating orchestrator-based data retrieval
|
||||
comprehensive_data = {
|
||||
"user_id": user_id,
|
||||
"strategy_data": {
|
||||
"industry": "technology",
|
||||
"target_audience": "professionals",
|
||||
"content_pillars": ["education", "insights", "trends"]
|
||||
},
|
||||
"gap_analysis": {
|
||||
"identified_gaps": ["content_type_1", "content_type_2"],
|
||||
"opportunities": ["trending_topics", "audience_needs"]
|
||||
},
|
||||
"performance_data": {
|
||||
"engagement_rate": 0.05,
|
||||
"top_performing_content": ["blog_posts", "social_media"]
|
||||
},
|
||||
"onboarding_data": {
|
||||
"target_audience": "professionals",
|
||||
"content_preferences": ["educational", "informative"]
|
||||
},
|
||||
"data_source": "12-step orchestrator"
|
||||
}
|
||||
|
||||
logger.info(f"Successfully retrieved comprehensive user data using orchestrator")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"data": comprehensive_data,
|
||||
"message": "Comprehensive user data retrieved successfully using orchestrator",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting comprehensive user data for user_id {user_id}: {str(e)}")
|
||||
logger.error(f"Exception type: {type(e)}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_comprehensive_user_data")
|
||||
|
||||
async def health_check(self) -> Dict[str, Any]:
|
||||
"""Health check for calendar generation services."""
|
||||
try:
|
||||
logger.info("🏥 Performing calendar generation health check")
|
||||
|
||||
# Check AI services
|
||||
from services.onboarding.api_key_manager import APIKeyManager
|
||||
api_manager = APIKeyManager()
|
||||
api_key_status = check_all_api_keys(api_manager)
|
||||
|
||||
# Check orchestrator status
|
||||
orchestrator_status = "healthy" if self.orchestrator else "unhealthy"
|
||||
|
||||
# Check database connectivity
|
||||
db_status = "healthy"
|
||||
try:
|
||||
# Test database connection - just check if db_session is available
|
||||
if self.db_session:
|
||||
# Simple connectivity test without hardcoded user_id
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
db_service = ContentPlanningDBService(self.db_session)
|
||||
# Don't test with a specific user_id - just verify service initializes
|
||||
db_status = "healthy"
|
||||
else:
|
||||
db_status = "no session"
|
||||
except Exception as e:
|
||||
db_status = f"error: {str(e)}"
|
||||
|
||||
health_status = {
|
||||
"service": "calendar_generation",
|
||||
"status": "healthy" if api_key_status.get("all_valid", False) and db_status == "healthy" and orchestrator_status == "healthy" else "unhealthy",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"components": {
|
||||
"ai_services": "healthy" if api_key_status.get("all_valid", False) else "unhealthy",
|
||||
"database": db_status,
|
||||
"orchestrator": orchestrator_status
|
||||
},
|
||||
"api_keys": api_key_status
|
||||
}
|
||||
|
||||
logger.info("✅ Calendar generation health check completed")
|
||||
return health_status
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Calendar generation health check failed: {str(e)}")
|
||||
return {
|
||||
"service": "calendar_generation",
|
||||
"status": "unhealthy",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
# Orchestrator Integration Methods
|
||||
|
||||
def initialize_orchestrator_session(self, session_id: str, request_data: Dict[str, Any]) -> bool:
|
||||
"""Initialize a new orchestrator session with duplicate prevention."""
|
||||
try:
|
||||
if not self.orchestrator:
|
||||
logger.error("❌ Orchestrator not initialized")
|
||||
return False
|
||||
|
||||
# Clean up old sessions for the same user
|
||||
user_id = request_data.get("user_id")
|
||||
if not user_id:
|
||||
logger.error("❌ user_id is required in request_data")
|
||||
return False
|
||||
self._cleanup_old_sessions(user_id)
|
||||
|
||||
# Check for existing active sessions for this user
|
||||
existing_session = self._get_active_session_for_user(user_id)
|
||||
if existing_session:
|
||||
logger.warning(f"⚠️ User {user_id} already has an active session: {existing_session}")
|
||||
return False
|
||||
|
||||
# Store session data
|
||||
self.orchestrator_sessions[session_id] = {
|
||||
"request_data": request_data,
|
||||
"user_id": user_id,
|
||||
"status": "initializing",
|
||||
"start_time": datetime.now(),
|
||||
"progress": {
|
||||
"current_step": 0,
|
||||
"overall_progress": 0,
|
||||
"step_results": {},
|
||||
"quality_scores": {},
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(f"✅ Orchestrator session {session_id} initialized for user {user_id}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to initialize orchestrator session: {e}")
|
||||
return False
|
||||
|
||||
def _cleanup_old_sessions(self, user_id: str) -> None:
|
||||
"""Clean up old sessions for a user."""
|
||||
try:
|
||||
current_time = datetime.now()
|
||||
sessions_to_remove = []
|
||||
|
||||
# Collect sessions to remove first, then remove them
|
||||
for session_id, session_data in self.orchestrator_sessions.items():
|
||||
if session_data.get("user_id") == user_id:
|
||||
start_time = session_data.get("start_time")
|
||||
if start_time:
|
||||
# Remove sessions older than 1 hour
|
||||
if (current_time - start_time).total_seconds() > 3600: # 1 hour
|
||||
sessions_to_remove.append(session_id)
|
||||
# Also remove completed/error sessions older than 10 minutes
|
||||
elif session_data.get("status") in ["completed", "error", "cancelled"]:
|
||||
if (current_time - start_time).total_seconds() > 600: # 10 minutes
|
||||
sessions_to_remove.append(session_id)
|
||||
|
||||
# Remove the sessions
|
||||
for session_id in sessions_to_remove:
|
||||
if session_id in self.orchestrator_sessions:
|
||||
del self.orchestrator_sessions[session_id]
|
||||
logger.info(f"🧹 Cleaned up old session: {session_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error cleaning up old sessions: {e}")
|
||||
|
||||
def _get_active_session_for_user(self, user_id: str) -> Optional[str]:
|
||||
"""Get active session for a user."""
|
||||
try:
|
||||
for session_id, session_data in self.orchestrator_sessions.items():
|
||||
if (session_data.get("user_id") == user_id and
|
||||
session_data.get("status") in ["initializing", "running"]):
|
||||
return session_id
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting active session for user: {e}")
|
||||
return None
|
||||
|
||||
async def start_orchestrator_generation(self, session_id: str, request_data: Dict[str, Any]) -> None:
|
||||
"""Start the 12-step calendar generation process."""
|
||||
try:
|
||||
if not self.orchestrator:
|
||||
logger.error("❌ Orchestrator not initialized")
|
||||
return
|
||||
|
||||
session = self.orchestrator_sessions.get(session_id)
|
||||
if not session:
|
||||
logger.error(f"❌ Session {session_id} not found")
|
||||
return
|
||||
|
||||
# Update session status
|
||||
session["status"] = "running"
|
||||
|
||||
# Start the 12-step process
|
||||
user_id = request_data.get("user_id")
|
||||
if not user_id:
|
||||
raise ValueError("user_id is required in request_data")
|
||||
|
||||
result = await self.orchestrator.generate_calendar(
|
||||
user_id=user_id,
|
||||
strategy_id=request_data.get("strategy_id"),
|
||||
calendar_type=request_data.get("calendar_type", "monthly"),
|
||||
industry=request_data.get("industry"),
|
||||
business_size=request_data.get("business_size", "sme"),
|
||||
progress_callback=lambda progress: self._update_session_progress(session_id, progress)
|
||||
)
|
||||
|
||||
# Update session with final result
|
||||
session["status"] = "completed"
|
||||
session["result"] = result
|
||||
session["end_time"] = datetime.now()
|
||||
|
||||
logger.info(f"✅ Orchestrator generation completed for session {session_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Orchestrator generation failed for session {session_id}: {e}")
|
||||
if session_id in self.orchestrator_sessions:
|
||||
self.orchestrator_sessions[session_id]["status"] = "error"
|
||||
self.orchestrator_sessions[session_id]["error"] = str(e)
|
||||
|
||||
def get_orchestrator_progress(self, session_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get progress for an orchestrator session."""
|
||||
try:
|
||||
logger.info(f"🔍 Looking for session {session_id}")
|
||||
logger.info(f"📊 Available sessions: {list(self.orchestrator_sessions.keys())}")
|
||||
|
||||
session = self.orchestrator_sessions.get(session_id)
|
||||
if not session:
|
||||
logger.warning(f"❌ Session {session_id} not found")
|
||||
return None
|
||||
|
||||
logger.info(f"✅ Found session {session_id} with status: {session['status']}")
|
||||
|
||||
# Ensure all required fields are present with default values
|
||||
progress_data = session.get("progress", {})
|
||||
|
||||
return {
|
||||
"status": session["status"],
|
||||
"current_step": progress_data.get("current_step", 0),
|
||||
"step_progress": progress_data.get("step_progress", 0), # Ensure this field is present
|
||||
"overall_progress": progress_data.get("overall_progress", 0),
|
||||
"step_results": progress_data.get("step_results", {}),
|
||||
"quality_scores": progress_data.get("quality_scores", {}),
|
||||
"errors": progress_data.get("errors", []),
|
||||
"warnings": progress_data.get("warnings", []),
|
||||
"transparency_messages": session.get("transparency_messages", []),
|
||||
"educational_content": session.get("educational_content", []),
|
||||
"estimated_completion": session.get("estimated_completion"),
|
||||
"last_updated": session.get("last_updated", datetime.now().isoformat())
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting orchestrator progress: {e}")
|
||||
return None
|
||||
|
||||
def _update_session_progress(self, session_id: str, progress: Dict[str, Any]) -> None:
|
||||
"""Update session progress from orchestrator callback."""
|
||||
try:
|
||||
session = self.orchestrator_sessions.get(session_id)
|
||||
if session:
|
||||
# Convert progress tracker format to service format
|
||||
current_step = progress.get("current_step", 0)
|
||||
total_steps = progress.get("total_steps", 12)
|
||||
step_progress = progress.get("step_progress", 0) # Get step-specific progress
|
||||
|
||||
session["progress"] = {
|
||||
"current_step": current_step,
|
||||
"step_progress": step_progress, # Add step_progress field
|
||||
"overall_progress": progress.get("progress_percentage", 0),
|
||||
"step_results": progress.get("step_details", {}),
|
||||
"quality_scores": {step: data.get("quality_score", 0.0) for step, data in progress.get("step_details", {}).items()},
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
}
|
||||
session["last_updated"] = datetime.now().isoformat()
|
||||
|
||||
logger.info(f"📊 Updated progress for session {session_id}: step {current_step}/{total_steps} (step progress: {step_progress}%)")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error updating session progress: {e}")
|
||||
|
||||
async def _save_calendar_to_db(self, user_id: str, strategy_id: Optional[int], calendar_data: Dict[str, Any], session_id: str) -> None:
|
||||
"""Save generated calendar to database."""
|
||||
try:
|
||||
if not self.db_session:
|
||||
logger.warning("⚠️ No database session available, skipping persistence")
|
||||
return
|
||||
|
||||
# Save session record
|
||||
session_record = CalendarGenerationSession(
|
||||
user_id=user_id,
|
||||
strategy_id=strategy_id,
|
||||
session_type=calendar_data.get("calendar_type", "monthly"),
|
||||
generation_params={"session_id": session_id},
|
||||
generated_calendar=calendar_data,
|
||||
ai_insights=calendar_data.get("ai_insights"),
|
||||
performance_predictions=calendar_data.get("performance_predictions"),
|
||||
content_themes=calendar_data.get("weekly_themes"),
|
||||
generation_status="completed",
|
||||
ai_confidence=calendar_data.get("ai_confidence"),
|
||||
processing_time=calendar_data.get("processing_time")
|
||||
)
|
||||
self.db_session.add(session_record)
|
||||
self.db_session.flush() # Get ID
|
||||
|
||||
# Save calendar events
|
||||
# Extract daily schedule from calendar data
|
||||
daily_schedule = calendar_data.get("daily_schedule", [])
|
||||
|
||||
# If daily_schedule is not directly available, try to extract from step results
|
||||
if not daily_schedule and "step_results" in calendar_data:
|
||||
daily_schedule = calendar_data.get("step_results", {}).get("step_08", {}).get("daily_schedule", [])
|
||||
|
||||
for day in daily_schedule:
|
||||
content_items = day.get("content_items", [])
|
||||
for item in content_items:
|
||||
# Parse date
|
||||
date_str = day.get("date")
|
||||
scheduled_date = datetime.utcnow()
|
||||
if date_str:
|
||||
try:
|
||||
scheduled_date = datetime.fromisoformat(date_str)
|
||||
except:
|
||||
pass
|
||||
|
||||
event = CalendarEvent(
|
||||
strategy_id=strategy_id if strategy_id else 0, # Fallback if no strategy
|
||||
title=item.get("title", "Untitled Event"),
|
||||
description=item.get("description"),
|
||||
content_type=item.get("type", "social_post"),
|
||||
platform=item.get("platform", "generic"),
|
||||
scheduled_date=scheduled_date,
|
||||
status="draft",
|
||||
ai_recommendations=item
|
||||
)
|
||||
self.db_session.add(event)
|
||||
|
||||
self.db_session.commit()
|
||||
logger.info(f"✅ Calendar saved to database for user {user_id}")
|
||||
|
||||
except Exception as e:
|
||||
self.db_session.rollback()
|
||||
logger.error(f"❌ Error saving calendar to database: {str(e)}")
|
||||
# Don't raise, just log error so we don't fail the request if persistence fails
|
||||
184
backend/api/content_planning/services/calendar_service.py
Normal file
184
backend/api/content_planning/services/calendar_service.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""
|
||||
Calendar Service for Content Planning API
|
||||
Extracted business logic from the calendar events route for better separation of concerns.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
# Import database service
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
|
||||
# Import utilities
|
||||
from ..utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ..utils.response_builders import ResponseBuilder
|
||||
from ..utils.constants import ERROR_MESSAGES, SUCCESS_MESSAGES
|
||||
|
||||
class CalendarService:
|
||||
"""Service class for calendar event operations."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def create_calendar_event(self, event_data: Dict[str, Any], db: Session) -> Dict[str, Any]:
|
||||
"""Create a new calendar event."""
|
||||
try:
|
||||
logger.info(f"Creating calendar event: {event_data.get('title', 'Unknown')}")
|
||||
|
||||
db_service = ContentPlanningDBService(db)
|
||||
created_event = await db_service.create_calendar_event(event_data)
|
||||
|
||||
if created_event:
|
||||
logger.info(f"Calendar event created successfully: {created_event.id}")
|
||||
return created_event.to_dict()
|
||||
else:
|
||||
raise Exception("Failed to create calendar event")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating calendar event: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "create_calendar_event")
|
||||
|
||||
async def get_calendar_events(self, strategy_id: Optional[int] = None, db: Session = None) -> List[Dict[str, Any]]:
|
||||
"""Get calendar events, optionally filtered by strategy."""
|
||||
try:
|
||||
logger.info("Fetching calendar events")
|
||||
|
||||
db_service = ContentPlanningDBService(db)
|
||||
|
||||
if strategy_id:
|
||||
events = await db_service.get_strategy_calendar_events(strategy_id)
|
||||
else:
|
||||
# TODO: Implement get_all_calendar_events method
|
||||
events = []
|
||||
|
||||
return [event.to_dict() for event in events]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting calendar events: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_calendar_events")
|
||||
|
||||
async def get_calendar_event_by_id(self, event_id: int, db: Session) -> Dict[str, Any]:
|
||||
"""Get a specific calendar event by ID."""
|
||||
try:
|
||||
logger.info(f"Fetching calendar event: {event_id}")
|
||||
|
||||
db_service = ContentPlanningDBService(db)
|
||||
event = await db_service.get_calendar_event(event_id)
|
||||
|
||||
if event:
|
||||
return event.to_dict()
|
||||
else:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Calendar event", event_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting calendar event: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_calendar_event_by_id")
|
||||
|
||||
async def update_calendar_event(self, event_id: int, update_data: Dict[str, Any], db: Session) -> Dict[str, Any]:
|
||||
"""Update a calendar event."""
|
||||
try:
|
||||
logger.info(f"Updating calendar event: {event_id}")
|
||||
|
||||
db_service = ContentPlanningDBService(db)
|
||||
updated_event = await db_service.update_calendar_event(event_id, update_data)
|
||||
|
||||
if updated_event:
|
||||
return updated_event.to_dict()
|
||||
else:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Calendar event", event_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating calendar event: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "update_calendar_event")
|
||||
|
||||
async def delete_calendar_event(self, event_id: int, db: Session) -> bool:
|
||||
"""Delete a calendar event."""
|
||||
try:
|
||||
logger.info(f"Deleting calendar event: {event_id}")
|
||||
|
||||
db_service = ContentPlanningDBService(db)
|
||||
deleted = await db_service.delete_calendar_event(event_id)
|
||||
|
||||
if deleted:
|
||||
return True
|
||||
else:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Calendar event", event_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting calendar event: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "delete_calendar_event")
|
||||
|
||||
async def get_events_by_status(self, strategy_id: int, status: str, db: Session) -> List[Dict[str, Any]]:
|
||||
"""Get calendar events by status for a specific strategy."""
|
||||
try:
|
||||
logger.info(f"Fetching events for strategy {strategy_id} with status {status}")
|
||||
|
||||
db_service = ContentPlanningDBService(db)
|
||||
events = await db_service.get_events_by_status(strategy_id, status)
|
||||
|
||||
return [event.to_dict() for event in events]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting events by status: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_events_by_status")
|
||||
|
||||
async def get_strategy_events(self, strategy_id: int, db: Session) -> Dict[str, Any]:
|
||||
"""Get calendar events for a specific strategy."""
|
||||
try:
|
||||
logger.info(f"Fetching events for strategy: {strategy_id}")
|
||||
|
||||
db_service = ContentPlanningDBService(db)
|
||||
events = await db_service.get_strategy_calendar_events(strategy_id)
|
||||
|
||||
return {
|
||||
'strategy_id': strategy_id,
|
||||
'events_count': len(events),
|
||||
'events': [event.to_dict() for event in events]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting strategy events: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_strategy_events")
|
||||
|
||||
async def schedule_event(self, event_data: Dict[str, Any], db: Session) -> Dict[str, Any]:
|
||||
"""Schedule a calendar event with conflict checking."""
|
||||
try:
|
||||
logger.info(f"Scheduling calendar event: {event_data.get('title', 'Unknown')}")
|
||||
|
||||
# Check for scheduling conflicts
|
||||
conflicts = await self._check_scheduling_conflicts(event_data, db)
|
||||
|
||||
if conflicts:
|
||||
logger.warning(f"Scheduling conflicts found: {conflicts}")
|
||||
return {
|
||||
"status": "conflict",
|
||||
"message": "Scheduling conflicts detected",
|
||||
"conflicts": conflicts,
|
||||
"event_data": event_data
|
||||
}
|
||||
|
||||
# Create the event
|
||||
created_event = await self.create_calendar_event(event_data, db)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Calendar event scheduled successfully",
|
||||
"event": created_event
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error scheduling calendar event: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "schedule_event")
|
||||
|
||||
async def _check_scheduling_conflicts(self, event_data: Dict[str, Any], db: Session) -> List[Dict[str, Any]]:
|
||||
"""Check for scheduling conflicts with existing events."""
|
||||
try:
|
||||
# This is a placeholder for conflict checking logic
|
||||
# In a real implementation, you would check for overlapping times, etc.
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking scheduling conflicts: {str(e)}")
|
||||
return []
|
||||
@@ -0,0 +1,346 @@
|
||||
# Content Strategy Implementation Status & Next Steps
|
||||
|
||||
## 📊 **Current Implementation Status**
|
||||
|
||||
### **✅ Completed (Phase 1 - Foundation)**
|
||||
|
||||
#### **1. Backend Cleanup & Reorganization** ✅
|
||||
- **✅ Deleted**: Old `strategy_service.py` (superseded by enhanced version)
|
||||
- **✅ Created**: Modular structure with 12 focused modules
|
||||
- **✅ Organized**: Related functionality into logical groups
|
||||
- **✅ Tested**: All imports and routes working correctly
|
||||
|
||||
#### **2. AI Analysis Module** ✅ **COMPLETE**
|
||||
- **✅ AI Recommendations Service**: 180 lines of comprehensive AI analysis
|
||||
- **✅ Prompt Engineering Service**: 150 lines of specialized prompt creation
|
||||
- **✅ Quality Validation Service**: 120 lines of quality assessment
|
||||
- **✅ 5 Analysis Types**: Comprehensive, Audience, Competitive, Performance, Calendar
|
||||
- **✅ Fallback System**: Robust error handling with fallback recommendations
|
||||
- **✅ Database Integration**: AI analysis result storage and retrieval
|
||||
|
||||
#### **3. Core Infrastructure** ✅
|
||||
- **✅ Core Strategy Service**: Main orchestration (188 lines)
|
||||
- **✅ Field Mappings**: Strategic input field definitions (50 lines)
|
||||
- **✅ Service Constants**: Configuration management (30 lines)
|
||||
- **✅ API Integration**: Enhanced strategy routes working
|
||||
|
||||
### **🔄 In Progress (Phase 2 - Core Modules)**
|
||||
|
||||
#### **1. Onboarding Module** 🔄 **HIGH PRIORITY**
|
||||
**Status**: Placeholder services created, needs implementation
|
||||
- **❌ Data Integration Service**: Needs real functionality
|
||||
- **❌ Field Transformation**: Needs logic implementation
|
||||
- **❌ Data Quality Assessment**: Needs quality scoring
|
||||
- **❌ Auto-Population**: Needs real data integration
|
||||
|
||||
**Next Steps**:
|
||||
```python
|
||||
# Priority 1: Implement data_integration.py
|
||||
- Extract onboarding data processing from monolithic file
|
||||
- Implement website analysis integration
|
||||
- Add research preferences processing
|
||||
- Create API keys data utilization
|
||||
|
||||
# Priority 2: Implement field_transformation.py
|
||||
- Create data to field mapping logic
|
||||
- Implement field transformation algorithms
|
||||
- Add validation and error handling
|
||||
- Test with real onboarding data
|
||||
|
||||
# Priority 3: Implement data_quality.py
|
||||
- Add completeness scoring
|
||||
- Implement confidence calculation
|
||||
- Create freshness evaluation
|
||||
- Add source attribution
|
||||
```
|
||||
|
||||
#### **2. Performance Module** 🔄 **HIGH PRIORITY**
|
||||
**Status**: Placeholder services created, needs implementation
|
||||
- **❌ Caching Service**: Needs Redis integration
|
||||
- **❌ Optimization Service**: Needs performance algorithms
|
||||
- **❌ Health Monitoring**: Needs system health checks
|
||||
- **❌ Metrics Collection**: Needs performance tracking
|
||||
|
||||
**Next Steps**:
|
||||
```python
|
||||
# Priority 1: Implement caching.py
|
||||
- Add Redis integration for AI analysis cache
|
||||
- Implement onboarding data cache (30 min TTL)
|
||||
- Add strategy cache (2 hours TTL)
|
||||
- Create intelligent cache eviction
|
||||
|
||||
# Priority 2: Implement optimization.py
|
||||
- Add response time optimization
|
||||
- Implement database query optimization
|
||||
- Create resource management
|
||||
- Add performance monitoring
|
||||
|
||||
# Priority 3: Implement health_monitoring.py
|
||||
- Add database health checks
|
||||
- Implement cache performance monitoring
|
||||
- Create AI service health assessment
|
||||
- Add response time tracking
|
||||
```
|
||||
|
||||
#### **3. Utils Module** 🔄 **HIGH PRIORITY**
|
||||
**Status**: Placeholder services created, needs implementation
|
||||
- **❌ Data Processors**: Needs utility functions
|
||||
- **❌ Validators**: Needs validation logic
|
||||
- **❌ Helper Methods**: Needs common utilities
|
||||
|
||||
**Next Steps**:
|
||||
```python
|
||||
# Priority 1: Implement data_processors.py
|
||||
- Add data transformation utilities
|
||||
- Create data cleaning functions
|
||||
- Implement data enrichment
|
||||
- Add data validation helpers
|
||||
|
||||
# Priority 2: Implement validators.py
|
||||
- Add field validation logic
|
||||
- Implement data type checking
|
||||
- Create business rule validation
|
||||
- Add error message generation
|
||||
```
|
||||
|
||||
### **📋 Pending (Phase 3 - Advanced Features)**
|
||||
|
||||
#### **1. Real AI Integration** 📋
|
||||
- **❌ OpenAI Integration**: Connect to actual AI services
|
||||
- **❌ Advanced Prompts**: Implement sophisticated prompt engineering
|
||||
- **❌ Machine Learning**: Add ML capabilities
|
||||
- **❌ Predictive Analytics**: Create predictive insights
|
||||
|
||||
#### **2. Enhanced Analytics** 📋
|
||||
- **❌ Real-time Tracking**: Implement live performance monitoring
|
||||
- **❌ Advanced Reporting**: Create comprehensive reports
|
||||
- **❌ Custom Dashboards**: Build user dashboards
|
||||
- **❌ Export Capabilities**: Add data export features
|
||||
|
||||
#### **3. User Experience** 📋
|
||||
- **❌ Progressive Disclosure**: Implement guided interface
|
||||
- **❌ Template Strategies**: Add pre-built strategy templates
|
||||
- **❌ Interactive Tutorials**: Create user onboarding
|
||||
- **❌ Smart Defaults**: Implement intelligent defaults
|
||||
|
||||
## 🎯 **Immediate Next Steps (Next 2-4 Weeks)**
|
||||
|
||||
### **Week 1-2: Complete Core Modules**
|
||||
|
||||
#### **1. Onboarding Integration** 🔥 **CRITICAL**
|
||||
```python
|
||||
# Day 1-2: Implement data_integration.py
|
||||
- Extract onboarding data processing from monolithic file
|
||||
- Implement website analysis integration
|
||||
- Add research preferences processing
|
||||
- Create API keys data utilization
|
||||
|
||||
# Day 3-4: Implement field_transformation.py
|
||||
- Create data to field mapping logic
|
||||
- Implement field transformation algorithms
|
||||
- Add validation and error handling
|
||||
- Test with real onboarding data
|
||||
|
||||
# Day 5-7: Implement data_quality.py
|
||||
- Add completeness scoring
|
||||
- Implement confidence calculation
|
||||
- Create freshness evaluation
|
||||
- Add source attribution
|
||||
```
|
||||
|
||||
#### **2. Performance Optimization** 🔥 **CRITICAL**
|
||||
```python
|
||||
# Day 1-2: Implement caching.py
|
||||
- Add Redis integration for AI analysis cache
|
||||
- Implement onboarding data cache (30 min TTL)
|
||||
- Add strategy cache (2 hours TTL)
|
||||
- Create intelligent cache eviction
|
||||
|
||||
# Day 3-4: Implement optimization.py
|
||||
- Add response time optimization
|
||||
- Implement database query optimization
|
||||
- Create resource management
|
||||
- Add performance monitoring
|
||||
|
||||
# Day 5-7: Implement health_monitoring.py
|
||||
- Add database health checks
|
||||
- Implement cache performance monitoring
|
||||
- Create AI service health assessment
|
||||
- Add response time tracking
|
||||
```
|
||||
|
||||
#### **3. Utils Implementation** 🔥 **CRITICAL**
|
||||
```python
|
||||
# Day 1-2: Implement data_processors.py
|
||||
- Add data transformation utilities
|
||||
- Create data cleaning functions
|
||||
- Implement data enrichment
|
||||
- Add data validation helpers
|
||||
|
||||
# Day 3-4: Implement validators.py
|
||||
- Add field validation logic
|
||||
- Implement data type checking
|
||||
- Create business rule validation
|
||||
- Add error message generation
|
||||
```
|
||||
|
||||
### **Week 3-4: Testing & Integration**
|
||||
|
||||
#### **1. Comprehensive Testing**
|
||||
```python
|
||||
# Unit Tests
|
||||
- Test each service independently
|
||||
- Add comprehensive test coverage
|
||||
- Implement mock services for testing
|
||||
- Create test data fixtures
|
||||
|
||||
# Integration Tests
|
||||
- Test service interactions
|
||||
- Verify API endpoints
|
||||
- Test database operations
|
||||
- Validate error handling
|
||||
|
||||
# End-to-End Tests
|
||||
- Test complete workflows
|
||||
- Verify user scenarios
|
||||
- Test performance under load
|
||||
- Validate real-world usage
|
||||
```
|
||||
|
||||
#### **2. Performance Optimization**
|
||||
```python
|
||||
# Performance Testing
|
||||
- Measure response times
|
||||
- Optimize database queries
|
||||
- Implement caching strategies
|
||||
- Monitor resource usage
|
||||
|
||||
# Load Testing
|
||||
- Test with multiple users
|
||||
- Verify scalability
|
||||
- Monitor memory usage
|
||||
- Optimize for production
|
||||
```
|
||||
|
||||
## 🚀 **Medium-term Goals (Next 2-3 Months)**
|
||||
|
||||
### **Phase 2: Enhanced Features**
|
||||
|
||||
#### **1. Real AI Integration**
|
||||
- [ ] Integrate with OpenAI API
|
||||
- [ ] Add Claude API integration
|
||||
- [ ] Implement advanced prompt engineering
|
||||
- [ ] Create machine learning capabilities
|
||||
|
||||
#### **2. Advanced Analytics**
|
||||
- [ ] Real-time performance tracking
|
||||
- [ ] Advanced reporting system
|
||||
- [ ] Custom dashboard creation
|
||||
- [ ] Data export capabilities
|
||||
|
||||
#### **3. User Experience Improvements**
|
||||
- [ ] Progressive disclosure implementation
|
||||
- [ ] Guided wizard interface
|
||||
- [ ] Template-based strategies
|
||||
- [ ] Interactive tutorials
|
||||
|
||||
### **Phase 3: Enterprise Features**
|
||||
|
||||
#### **1. Advanced AI Capabilities**
|
||||
- [ ] Multi-model AI integration
|
||||
- [ ] Custom model training
|
||||
- [ ] Advanced analytics
|
||||
- [ ] Predictive insights
|
||||
|
||||
#### **2. Collaboration Features**
|
||||
- [ ] Team collaboration tools
|
||||
- [ ] Strategy sharing
|
||||
- [ ] Version control
|
||||
- [ ] Approval workflows
|
||||
|
||||
#### **3. Enterprise Integration**
|
||||
- [ ] CRM integration
|
||||
- [ ] Marketing automation
|
||||
- [ ] Analytics platforms
|
||||
- [ ] Custom API endpoints
|
||||
|
||||
## 📈 **Success Metrics & KPIs**
|
||||
|
||||
### **Technical Metrics**
|
||||
- **Response Time**: < 2 seconds for strategy creation
|
||||
- **Cache Hit Rate**: > 80% for frequently accessed data
|
||||
- **Error Rate**: < 1% for all operations
|
||||
- **Uptime**: > 99.9% availability
|
||||
|
||||
### **Quality Metrics**
|
||||
- **AI Response Quality**: > 85% confidence scores
|
||||
- **Data Completeness**: > 90% field completion
|
||||
- **User Satisfaction**: > 4.5/5 rating
|
||||
- **Strategy Effectiveness**: Measurable ROI improvements
|
||||
|
||||
### **Business Metrics**
|
||||
- **User Adoption**: Growing user base
|
||||
- **Feature Usage**: High engagement with AI features
|
||||
- **Customer Retention**: > 90% monthly retention
|
||||
- **Revenue Impact**: Measurable business value
|
||||
|
||||
## 🔧 **Development Guidelines**
|
||||
|
||||
### **1. Code Quality Standards**
|
||||
- **Type Hints**: Use comprehensive type annotations
|
||||
- **Documentation**: Document all public methods
|
||||
- **Error Handling**: Implement robust error handling
|
||||
- **Logging**: Add comprehensive logging
|
||||
|
||||
### **2. Testing Strategy**
|
||||
- **Unit Tests**: Test each service independently
|
||||
- **Integration Tests**: Test service interactions
|
||||
- **End-to-End Tests**: Test complete workflows
|
||||
- **Performance Tests**: Monitor response times
|
||||
|
||||
### **3. Performance Considerations**
|
||||
- **Caching**: Implement intelligent caching strategies
|
||||
- **Database Optimization**: Use efficient queries
|
||||
- **Async Operations**: Use async/await for I/O operations
|
||||
- **Resource Management**: Properly manage memory and connections
|
||||
|
||||
## 🎯 **Risk Assessment & Mitigation**
|
||||
|
||||
### **High Risk Items**
|
||||
1. **Onboarding Integration Complexity**: Mitigation - Start with simple implementations
|
||||
2. **Performance Optimization**: Mitigation - Implement caching first
|
||||
3. **AI Service Integration**: Mitigation - Use fallback systems
|
||||
4. **Database Performance**: Mitigation - Optimize queries and add indexing
|
||||
|
||||
### **Medium Risk Items**
|
||||
1. **User Experience**: Mitigation - Implement progressive disclosure
|
||||
2. **Data Quality**: Mitigation - Add comprehensive validation
|
||||
3. **Scalability**: Mitigation - Design for horizontal scaling
|
||||
4. **Maintenance**: Mitigation - Comprehensive documentation and testing
|
||||
|
||||
## 📋 **Resource Requirements**
|
||||
|
||||
### **Development Team**
|
||||
- **Backend Developer**: 1-2 developers for core modules
|
||||
- **AI Specialist**: 1 developer for AI integration
|
||||
- **DevOps Engineer**: 1 engineer for deployment and monitoring
|
||||
- **QA Engineer**: 1 engineer for testing and quality assurance
|
||||
|
||||
### **Infrastructure**
|
||||
- **Database**: PostgreSQL with proper indexing
|
||||
- **Cache**: Redis for performance optimization
|
||||
- **AI Services**: OpenAI/Claude API integration
|
||||
- **Monitoring**: Application performance monitoring
|
||||
|
||||
### **Timeline**
|
||||
- **Phase 1 (Core Modules)**: 2-4 weeks
|
||||
- **Phase 2 (Enhanced Features)**: 2-3 months
|
||||
- **Phase 3 (Enterprise Features)**: 6-12 months
|
||||
|
||||
## 🎉 **Conclusion**
|
||||
|
||||
The Content Strategy Services have a solid foundation with the AI Analysis module complete and the core infrastructure in place. The immediate priority is to complete the Onboarding, Performance, and Utils modules to create a fully functional system. With proper implementation of the next steps, the system will provide enterprise-level content strategy capabilities to solopreneurs and small businesses.
|
||||
|
||||
**Current Status**: 40% Complete (Foundation + AI Analysis)
|
||||
**Next Milestone**: 70% Complete (Core Modules)
|
||||
**Target Completion**: 100% Complete (All Features)
|
||||
363
backend/api/content_planning/services/content_strategy/README.md
Normal file
363
backend/api/content_planning/services/content_strategy/README.md
Normal file
@@ -0,0 +1,363 @@
|
||||
# Content Strategy Services
|
||||
|
||||
## 🎯 **Overview**
|
||||
|
||||
The Content Strategy Services module provides comprehensive content strategy management with 30+ strategic inputs, AI-powered recommendations, and enterprise-level analysis capabilities. This modular architecture enables solopreneurs, small business owners, and startups to access expert-level content strategy without requiring expensive digital marketing teams.
|
||||
|
||||
## 🏗️ **Architecture**
|
||||
|
||||
```
|
||||
content_strategy/
|
||||
├── core/ # Main orchestration & configuration
|
||||
│ ├── strategy_service.py # Main service orchestration
|
||||
│ ├── field_mappings.py # Strategic input field definitions
|
||||
│ └── constants.py # Service configuration
|
||||
├── ai_analysis/ # AI recommendation generation
|
||||
│ ├── ai_recommendations.py # Comprehensive AI analysis
|
||||
│ ├── prompt_engineering.py # Specialized prompt creation
|
||||
│ └── quality_validation.py # Quality assessment & scoring
|
||||
├── onboarding/ # Onboarding data integration
|
||||
│ ├── data_integration.py # Onboarding data processing
|
||||
│ ├── field_transformation.py # Data to field mapping
|
||||
│ └── data_quality.py # Quality assessment
|
||||
├── performance/ # Performance optimization
|
||||
│ ├── caching.py # Cache management
|
||||
│ ├── optimization.py # Performance optimization
|
||||
│ └── health_monitoring.py # System health checks
|
||||
└── utils/ # Data processing utilities
|
||||
├── data_processors.py # Data processing utilities
|
||||
└── validators.py # Data validation
|
||||
```
|
||||
|
||||
## 🚀 **Key Features**
|
||||
|
||||
### **1. Comprehensive Strategic Inputs (30+ Fields)**
|
||||
|
||||
#### **Business Context**
|
||||
- Business Objectives & Target Metrics
|
||||
- Content Budget & Team Size
|
||||
- Implementation Timeline & Market Share
|
||||
- Competitive Position & Performance Metrics
|
||||
|
||||
#### **Audience Intelligence**
|
||||
- Content Preferences & Consumption Patterns
|
||||
- Audience Pain Points & Buying Journey
|
||||
- Seasonal Trends & Engagement Metrics
|
||||
|
||||
#### **Competitive Intelligence**
|
||||
- Top Competitors & Competitor Strategies
|
||||
- Market Gaps & Industry Trends
|
||||
- Emerging Trends Analysis
|
||||
|
||||
#### **Content Strategy**
|
||||
- Preferred Formats & Content Mix
|
||||
- Content Frequency & Optimal Timing
|
||||
- Quality Metrics & Editorial Guidelines
|
||||
- Brand Voice Definition
|
||||
|
||||
#### **Performance Analytics**
|
||||
- Traffic Sources & Conversion Rates
|
||||
- Content ROI Targets & A/B Testing
|
||||
|
||||
### **2. AI-Powered Recommendations**
|
||||
|
||||
#### **Comprehensive Analysis Types**
|
||||
- **Comprehensive Strategy**: Full strategic positioning and market analysis
|
||||
- **Audience Intelligence**: Detailed audience persona development
|
||||
- **Competitive Intelligence**: Competitor analysis and market positioning
|
||||
- **Performance Optimization**: Traffic and conversion optimization
|
||||
- **Content Calendar Optimization**: Scheduling and timing optimization
|
||||
|
||||
#### **Quality Assessment**
|
||||
- AI Response Quality Validation
|
||||
- Strategic Score Calculation
|
||||
- Market Positioning Analysis
|
||||
- Competitive Advantage Extraction
|
||||
- Risk Assessment & Opportunity Analysis
|
||||
|
||||
### **3. Onboarding Data Integration**
|
||||
|
||||
#### **Smart Auto-Population**
|
||||
- Website Analysis Integration
|
||||
- Research Preferences Processing
|
||||
- API Keys Data Utilization
|
||||
- Field Transformation & Mapping
|
||||
|
||||
#### **Data Quality Assessment**
|
||||
- Completeness Scoring
|
||||
- Confidence Level Calculation
|
||||
- Data Freshness Evaluation
|
||||
- Source Attribution
|
||||
|
||||
### **4. Performance Optimization**
|
||||
|
||||
#### **Caching System**
|
||||
- AI Analysis Cache (1 hour TTL)
|
||||
- Onboarding Data Cache (30 minutes TTL)
|
||||
- Strategy Cache (2 hours TTL)
|
||||
- Intelligent Cache Eviction
|
||||
|
||||
#### **Health Monitoring**
|
||||
- Database Health Checks
|
||||
- Cache Performance Monitoring
|
||||
- AI Service Health Assessment
|
||||
- Response Time Optimization
|
||||
|
||||
## 📊 **Current Implementation Status**
|
||||
|
||||
### **✅ Completed Features**
|
||||
|
||||
#### **1. Core Infrastructure**
|
||||
- [x] Modular service architecture
|
||||
- [x] Core strategy service orchestration
|
||||
- [x] Strategic input field definitions
|
||||
- [x] Service configuration management
|
||||
|
||||
#### **2. AI Analysis Module**
|
||||
- [x] AI recommendations service (180 lines)
|
||||
- [x] Prompt engineering service (150 lines)
|
||||
- [x] Quality validation service (120 lines)
|
||||
- [x] 5 specialized analysis types
|
||||
- [x] Fallback recommendation system
|
||||
- [x] Quality assessment capabilities
|
||||
|
||||
#### **3. Database Integration**
|
||||
- [x] Enhanced strategy models
|
||||
- [x] AI analysis result storage
|
||||
- [x] Onboarding data integration
|
||||
- [x] Performance metrics tracking
|
||||
|
||||
#### **4. API Integration**
|
||||
- [x] Enhanced strategy routes
|
||||
- [x] Onboarding data endpoints
|
||||
- [x] AI analytics endpoints
|
||||
- [x] Performance monitoring endpoints
|
||||
|
||||
### **🔄 In Progress**
|
||||
|
||||
#### **1. Onboarding Module**
|
||||
- [ ] Data integration service implementation
|
||||
- [ ] Field transformation logic
|
||||
- [ ] Data quality assessment
|
||||
- [ ] Auto-population functionality
|
||||
|
||||
#### **2. Performance Module**
|
||||
- [ ] Caching service implementation
|
||||
- [ ] Optimization algorithms
|
||||
- [ ] Health monitoring system
|
||||
- [ ] Performance metrics collection
|
||||
|
||||
#### **3. Utils Module**
|
||||
- [ ] Data processing utilities
|
||||
- [ ] Validation functions
|
||||
- [ ] Helper methods
|
||||
|
||||
### **📋 Pending Implementation**
|
||||
|
||||
#### **1. Advanced AI Features**
|
||||
- [ ] Real AI service integration
|
||||
- [ ] Advanced prompt engineering
|
||||
- [ ] Machine learning models
|
||||
- [ ] Predictive analytics
|
||||
|
||||
#### **2. Enhanced Analytics**
|
||||
- [ ] Real-time performance tracking
|
||||
- [ ] Advanced reporting
|
||||
- [ ] Custom dashboards
|
||||
- [ ] Export capabilities
|
||||
|
||||
#### **3. User Experience**
|
||||
- [ ] Progressive disclosure
|
||||
- [ ] Guided wizard interface
|
||||
- [ ] Template-based strategies
|
||||
- [ ] Interactive tutorials
|
||||
|
||||
## 🎯 **Next Steps Priority**
|
||||
|
||||
### **Phase 1: Complete Core Modules (Immediate)**
|
||||
|
||||
#### **1. Onboarding Integration** 🔥 **HIGH PRIORITY**
|
||||
```python
|
||||
# Priority: Complete onboarding data integration
|
||||
- Implement data_integration.py with real functionality
|
||||
- Add field_transformation.py logic
|
||||
- Implement data_quality.py assessment
|
||||
- Test auto-population with real data
|
||||
```
|
||||
|
||||
#### **2. Performance Optimization** 🔥 **HIGH PRIORITY**
|
||||
```python
|
||||
# Priority: Implement caching and optimization
|
||||
- Complete caching.py with Redis integration
|
||||
- Add optimization.py algorithms
|
||||
- Implement health_monitoring.py
|
||||
- Add performance metrics collection
|
||||
```
|
||||
|
||||
#### **3. Utils Implementation** 🔥 **HIGH PRIORITY**
|
||||
```python
|
||||
# Priority: Add utility functions
|
||||
- Implement data_processors.py
|
||||
- Add validators.py functions
|
||||
- Create helper methods
|
||||
- Add comprehensive error handling
|
||||
```
|
||||
|
||||
### **Phase 2: Enhanced Features (Short-term)**
|
||||
|
||||
#### **1. Real AI Integration**
|
||||
- [ ] Integrate with actual AI services (OpenAI, Claude, etc.)
|
||||
- [ ] Implement advanced prompt engineering
|
||||
- [ ] Add machine learning capabilities
|
||||
- [ ] Create predictive analytics
|
||||
|
||||
#### **2. Advanced Analytics**
|
||||
- [ ] Real-time performance tracking
|
||||
- [ ] Advanced reporting system
|
||||
- [ ] Custom dashboard creation
|
||||
- [ ] Data export capabilities
|
||||
|
||||
#### **3. User Experience Improvements**
|
||||
- [ ] Progressive disclosure implementation
|
||||
- [ ] Guided wizard interface
|
||||
- [ ] Template-based strategies
|
||||
- [ ] Interactive tutorials
|
||||
|
||||
### **Phase 3: Enterprise Features (Long-term)**
|
||||
|
||||
#### **1. Advanced AI Capabilities**
|
||||
- [ ] Multi-model AI integration
|
||||
- [ ] Custom model training
|
||||
- [ ] Advanced analytics
|
||||
- [ ] Predictive insights
|
||||
|
||||
#### **2. Collaboration Features**
|
||||
- [ ] Team collaboration tools
|
||||
- [ ] Strategy sharing
|
||||
- [ ] Version control
|
||||
- [ ] Approval workflows
|
||||
|
||||
#### **3. Enterprise Integration**
|
||||
- [ ] CRM integration
|
||||
- [ ] Marketing automation
|
||||
- [ ] Analytics platforms
|
||||
- [ ] Custom API endpoints
|
||||
|
||||
## 🔧 **Development Guidelines**
|
||||
|
||||
### **1. Module Boundaries**
|
||||
- **Respect service responsibilities**: Each module has clear boundaries
|
||||
- **Use dependency injection**: Services should be loosely coupled
|
||||
- **Follow single responsibility**: Each service has one primary purpose
|
||||
- **Maintain clear interfaces**: Well-defined method signatures
|
||||
|
||||
### **2. Testing Strategy**
|
||||
- **Unit tests**: Test each service independently
|
||||
- **Integration tests**: Test service interactions
|
||||
- **End-to-end tests**: Test complete workflows
|
||||
- **Performance tests**: Monitor response times
|
||||
|
||||
### **3. Code Quality**
|
||||
- **Type hints**: Use comprehensive type annotations
|
||||
- **Documentation**: Document all public methods
|
||||
- **Error handling**: Implement robust error handling
|
||||
- **Logging**: Add comprehensive logging
|
||||
|
||||
### **4. Performance Considerations**
|
||||
- **Caching**: Implement intelligent caching strategies
|
||||
- **Database optimization**: Use efficient queries
|
||||
- **Async operations**: Use async/await for I/O operations
|
||||
- **Resource management**: Properly manage memory and connections
|
||||
|
||||
## 📈 **Success Metrics**
|
||||
|
||||
### **1. Performance Metrics**
|
||||
- **Response Time**: < 2 seconds for strategy creation
|
||||
- **Cache Hit Rate**: > 80% for frequently accessed data
|
||||
- **Error Rate**: < 1% for all operations
|
||||
- **Uptime**: > 99.9% availability
|
||||
|
||||
### **2. Quality Metrics**
|
||||
- **AI Response Quality**: > 85% confidence scores
|
||||
- **Data Completeness**: > 90% field completion
|
||||
- **User Satisfaction**: > 4.5/5 rating
|
||||
- **Strategy Effectiveness**: Measurable ROI improvements
|
||||
|
||||
### **3. Business Metrics**
|
||||
- **User Adoption**: Growing user base
|
||||
- **Feature Usage**: High engagement with AI features
|
||||
- **Customer Retention**: > 90% monthly retention
|
||||
- **Revenue Impact**: Measurable business value
|
||||
|
||||
## 🚀 **Getting Started**
|
||||
|
||||
### **1. Setup Development Environment**
|
||||
```bash
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Set up database
|
||||
python manage.py migrate
|
||||
|
||||
# Run tests
|
||||
python -m pytest tests/
|
||||
```
|
||||
|
||||
### **2. Run the Service**
|
||||
```bash
|
||||
# Start the development server
|
||||
uvicorn main:app --reload
|
||||
|
||||
# Access the API
|
||||
curl http://localhost:8000/api/content-planning/strategies/
|
||||
```
|
||||
|
||||
### **3. Test AI Features**
|
||||
```python
|
||||
# Create a strategy with AI recommendations
|
||||
from api.content_planning.services.content_strategy import EnhancedStrategyService
|
||||
|
||||
service = EnhancedStrategyService()
|
||||
strategy = await service.create_enhanced_strategy(strategy_data, db)
|
||||
```
|
||||
|
||||
## 📚 **Documentation**
|
||||
|
||||
- **API Documentation**: `/docs` endpoint for interactive API docs
|
||||
- **Code Documentation**: Comprehensive docstrings in all modules
|
||||
- **Architecture Guide**: Detailed system architecture documentation
|
||||
- **User Guide**: Step-by-step user instructions
|
||||
|
||||
## 🤝 **Contributing**
|
||||
|
||||
### **1. Development Workflow**
|
||||
- Create feature branches from `main`
|
||||
- Write comprehensive tests
|
||||
- Update documentation
|
||||
- Submit pull requests
|
||||
|
||||
### **2. Code Review Process**
|
||||
- All changes require code review
|
||||
- Automated testing must pass
|
||||
- Documentation must be updated
|
||||
- Performance impact must be assessed
|
||||
|
||||
### **3. Release Process**
|
||||
- Semantic versioning
|
||||
- Changelog maintenance
|
||||
- Automated deployment
|
||||
- Rollback procedures
|
||||
|
||||
## 📞 **Support**
|
||||
|
||||
For questions, issues, or contributions:
|
||||
- **Issues**: Create GitHub issues for bugs or feature requests
|
||||
- **Discussions**: Use GitHub discussions for questions
|
||||
- **Documentation**: Check the comprehensive documentation
|
||||
- **Community**: Join our developer community
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: August 2024
|
||||
**Version**: 1.0.0
|
||||
**Status**: Active Development
|
||||
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
Content Strategy Module
|
||||
Modular implementation of enhanced content strategy services.
|
||||
"""
|
||||
|
||||
from .core.strategy_service import EnhancedStrategyService as ModularEnhancedStrategyService
|
||||
|
||||
__all__ = ['ModularEnhancedStrategyService']
|
||||
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
AI Analysis Module
|
||||
AI-powered analysis and recommendations for content strategy.
|
||||
"""
|
||||
|
||||
from .ai_recommendations import AIRecommendationsService
|
||||
from .quality_validation import QualityValidationService
|
||||
from .strategic_intelligence_analyzer import StrategicIntelligenceAnalyzer
|
||||
from .content_distribution_analyzer import ContentDistributionAnalyzer
|
||||
from .prompt_engineering import PromptEngineeringService
|
||||
from .strategy_analyzer import (
|
||||
StrategyAnalyzer,
|
||||
generate_comprehensive_ai_recommendations,
|
||||
generate_specialized_recommendations,
|
||||
create_specialized_prompt,
|
||||
call_ai_service,
|
||||
parse_ai_response,
|
||||
get_fallback_recommendations,
|
||||
get_latest_ai_analysis,
|
||||
get_onboarding_integration
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'AIRecommendationsService',
|
||||
'QualityValidationService',
|
||||
'StrategicIntelligenceAnalyzer',
|
||||
'ContentDistributionAnalyzer',
|
||||
'PromptEngineeringService',
|
||||
'StrategyAnalyzer',
|
||||
'generate_comprehensive_ai_recommendations',
|
||||
'generate_specialized_recommendations',
|
||||
'create_specialized_prompt',
|
||||
'call_ai_service',
|
||||
'parse_ai_response',
|
||||
'get_fallback_recommendations',
|
||||
'get_latest_ai_analysis',
|
||||
'get_onboarding_integration'
|
||||
]
|
||||
@@ -0,0 +1,148 @@
|
||||
"""
|
||||
AI Recommendations Service
|
||||
AI recommendation generation and analysis.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any, Optional, List
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
# Import database models
|
||||
from models.enhanced_strategy_models import EnhancedContentStrategy, EnhancedAIAnalysisResult
|
||||
|
||||
# Import modular components
|
||||
from .prompt_engineering import PromptEngineeringService
|
||||
from .quality_validation import QualityValidationService
|
||||
from .strategic_intelligence_analyzer import StrategicIntelligenceAnalyzer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class AIRecommendationsService:
|
||||
"""Service for AI recommendation generation."""
|
||||
|
||||
def __init__(self):
|
||||
self.prompt_engineering_service = PromptEngineeringService()
|
||||
self.quality_validation_service = QualityValidationService()
|
||||
self.strategic_intelligence_analyzer = StrategicIntelligenceAnalyzer()
|
||||
|
||||
# Analysis types for comprehensive recommendations
|
||||
self.analysis_types = [
|
||||
'comprehensive_strategy',
|
||||
'audience_intelligence',
|
||||
'competitive_intelligence',
|
||||
'performance_optimization',
|
||||
'content_calendar_optimization'
|
||||
]
|
||||
|
||||
async def _call_ai_service(self, prompt: str, analysis_type: str) -> Dict[str, Any]:
|
||||
"""Call AI service to generate recommendations."""
|
||||
try:
|
||||
# Import AI service manager
|
||||
from services.ai_service_manager import AIServiceManager
|
||||
|
||||
# Initialize AI service
|
||||
ai_service = AIServiceManager()
|
||||
|
||||
# Generate AI response based on analysis type
|
||||
if analysis_type == "strategic_intelligence":
|
||||
response = await ai_service.generate_strategic_intelligence({
|
||||
"prompt": prompt,
|
||||
"analysis_type": analysis_type
|
||||
})
|
||||
elif analysis_type == "content_recommendations":
|
||||
response = await ai_service.generate_content_recommendations({
|
||||
"prompt": prompt,
|
||||
"analysis_type": analysis_type
|
||||
})
|
||||
elif analysis_type == "market_analysis":
|
||||
response = await ai_service.generate_market_position_analysis({
|
||||
"prompt": prompt,
|
||||
"analysis_type": analysis_type
|
||||
})
|
||||
else:
|
||||
# Default to strategic intelligence
|
||||
response = await ai_service.generate_strategic_intelligence({
|
||||
"prompt": prompt,
|
||||
"analysis_type": analysis_type
|
||||
})
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calling AI service: {str(e)}")
|
||||
raise Exception(f"Failed to generate AI recommendations: {str(e)}")
|
||||
|
||||
def _parse_ai_response(self, ai_response: Dict[str, Any], analysis_type: str) -> Dict[str, Any]:
|
||||
return ai_response # parsing now handled downstream
|
||||
|
||||
def get_output_schema(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"type": "object",
|
||||
"required": ["strategy_brief", "channels", "pillars", "plan_30_60_90", "kpis"],
|
||||
"properties": {
|
||||
"strategy_brief": {"type": "object"},
|
||||
"channels": {"type": "array", "items": {"type": "object"}},
|
||||
"pillars": {"type": "array", "items": {"type": "object"}},
|
||||
"plan_30_60_90": {"type": "object"},
|
||||
"kpis": {"type": "object"},
|
||||
"citations": {"type": "array", "items": {"type": "object"}}
|
||||
}
|
||||
}
|
||||
|
||||
async def generate_comprehensive_ai_recommendations(self, strategy: EnhancedContentStrategy, db: Session) -> None:
|
||||
try:
|
||||
# Build centralized prompts per analysis type
|
||||
prompt = self.prompt_engineering_service.create_specialized_prompt(strategy, "comprehensive_strategy")
|
||||
raw = await self._call_ai_service(prompt, "strategic_intelligence")
|
||||
# Validate against schema
|
||||
schema = self.get_output_schema()
|
||||
self.quality_validation_service.validate_against_schema(raw, schema)
|
||||
# Persist
|
||||
result = EnhancedAIAnalysisResult(
|
||||
strategy_id=strategy.id,
|
||||
analysis_type="comprehensive_strategy",
|
||||
result_json=raw,
|
||||
created_at=datetime.utcnow()
|
||||
)
|
||||
db.add(result)
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Comprehensive recommendation generation failed: {str(e)}")
|
||||
raise
|
||||
|
||||
async def _generate_specialized_recommendations(self, strategy: EnhancedContentStrategy, analysis_type: str, db: Session) -> Dict[str, Any]:
|
||||
"""Generate specialized recommendations using specific AI prompts."""
|
||||
try:
|
||||
# Prepare strategy data for AI analysis
|
||||
strategy_data = strategy.to_dict()
|
||||
|
||||
# Create prompt based on analysis type
|
||||
prompt = self.prompt_engineering_service.create_specialized_prompt(strategy, analysis_type)
|
||||
|
||||
# Generate AI response
|
||||
ai_response = await self._call_ai_service(prompt, analysis_type)
|
||||
|
||||
# Parse and structure the response
|
||||
structured_response = self._parse_ai_response(ai_response, analysis_type)
|
||||
|
||||
return structured_response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating {analysis_type} recommendations: {str(e)}")
|
||||
# Raise exception instead of returning fallback data
|
||||
raise Exception(f"Failed to generate {analysis_type} recommendations: {str(e)}")
|
||||
|
||||
async def get_latest_ai_analysis(self, strategy_id: int, db: Session) -> Optional[Dict[str, Any]]:
|
||||
"""Get latest AI analysis for a strategy."""
|
||||
try:
|
||||
analysis = db.query(EnhancedAIAnalysisResult).filter(
|
||||
EnhancedAIAnalysisResult.strategy_id == strategy_id
|
||||
).order_by(EnhancedAIAnalysisResult.created_at.desc()).first()
|
||||
|
||||
return analysis.to_dict() if analysis else None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting latest AI analysis: {str(e)}")
|
||||
return None
|
||||
@@ -0,0 +1,261 @@
|
||||
"""
|
||||
Content Distribution Analyzer
|
||||
Handles content distribution strategy analysis and optimization.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ContentDistributionAnalyzer:
|
||||
"""Analyzes and generates content distribution strategies."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def analyze_content_distribution(self, preferred_formats: list, content_frequency: str, industry: str, team_size: int) -> Dict[str, Any]:
|
||||
"""Analyze content distribution strategy for personalized insights."""
|
||||
distribution_channels = []
|
||||
|
||||
# Social media platforms
|
||||
if 'video' in preferred_formats:
|
||||
distribution_channels.extend([
|
||||
{
|
||||
"platform": "TikTok",
|
||||
"priority": "High",
|
||||
"content_type": "Short-form video",
|
||||
"posting_frequency": "Daily",
|
||||
"best_practices": ["Use trending sounds", "Create educational content", "Engage with comments"],
|
||||
"free_tools": ["TikTok Creator Studio", "CapCut"],
|
||||
"expected_reach": "10K-100K views per video"
|
||||
},
|
||||
{
|
||||
"platform": "Instagram Reels",
|
||||
"priority": "High",
|
||||
"content_type": "Short-form video",
|
||||
"posting_frequency": "Daily",
|
||||
"best_practices": ["Use trending hashtags", "Create behind-the-scenes content", "Cross-promote"],
|
||||
"free_tools": ["Instagram Insights", "Canva"],
|
||||
"expected_reach": "5K-50K views per reel"
|
||||
}
|
||||
])
|
||||
|
||||
# Blog and written content
|
||||
if 'blog' in preferred_formats or 'article' in preferred_formats:
|
||||
distribution_channels.append({
|
||||
"platform": "Personal Blog/Website",
|
||||
"priority": "High",
|
||||
"content_type": "Long-form articles",
|
||||
"posting_frequency": "Weekly",
|
||||
"best_practices": ["SEO optimization", "Email list building", "Social sharing"],
|
||||
"free_tools": ["WordPress.com", "Medium", "Substack"],
|
||||
"expected_reach": "1K-10K monthly readers"
|
||||
})
|
||||
|
||||
# Podcast distribution
|
||||
distribution_channels.append({
|
||||
"platform": "Podcast",
|
||||
"priority": "Medium",
|
||||
"content_type": "Audio content",
|
||||
"posting_frequency": "Weekly",
|
||||
"best_practices": ["Consistent publishing", "Guest interviews", "Cross-promotion"],
|
||||
"free_tools": ["Anchor", "Spotify for Podcasters", "Riverside"],
|
||||
"expected_reach": "500-5K monthly listeners"
|
||||
})
|
||||
|
||||
# Email newsletter
|
||||
distribution_channels.append({
|
||||
"platform": "Email Newsletter",
|
||||
"priority": "High",
|
||||
"content_type": "Personal updates and insights",
|
||||
"posting_frequency": "Weekly",
|
||||
"best_practices": ["Personal storytelling", "Exclusive content", "Call-to-action"],
|
||||
"free_tools": ["Mailchimp", "ConvertKit", "Substack"],
|
||||
"expected_reach": "100-1K subscribers"
|
||||
})
|
||||
|
||||
return {
|
||||
"distribution_channels": distribution_channels,
|
||||
"optimal_posting_schedule": self._generate_posting_schedule(content_frequency, team_size),
|
||||
"cross_promotion_strategy": self._generate_cross_promotion_strategy(preferred_formats),
|
||||
"content_repurposing_plan": self._generate_repurposing_plan(preferred_formats),
|
||||
"audience_growth_tactics": [
|
||||
"Collaborate with other creators in your niche",
|
||||
"Participate in industry hashtags and challenges",
|
||||
"Create shareable content that provides value",
|
||||
"Engage with your audience in comments and DMs",
|
||||
"Use trending topics to create relevant content"
|
||||
]
|
||||
}
|
||||
|
||||
def _generate_posting_schedule(self, content_frequency: str, team_size: int) -> Dict[str, Any]:
|
||||
"""Generate optimal posting schedule for personalized insights."""
|
||||
if team_size == 1:
|
||||
return {
|
||||
"monday": "Educational content or industry insights",
|
||||
"tuesday": "Behind-the-scenes or personal story",
|
||||
"wednesday": "Problem-solving content or tips",
|
||||
"thursday": "Community engagement or Q&A",
|
||||
"friday": "Weekend inspiration or fun content",
|
||||
"saturday": "Repurpose best-performing content",
|
||||
"sunday": "Planning and content creation"
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"monday": "Weekly theme announcement",
|
||||
"tuesday": "Educational content",
|
||||
"wednesday": "Interactive content",
|
||||
"thursday": "Behind-the-scenes",
|
||||
"friday": "Community highlights",
|
||||
"saturday": "Repurposed content",
|
||||
"sunday": "Planning and creation"
|
||||
}
|
||||
|
||||
def _generate_cross_promotion_strategy(self, preferred_formats: list) -> List[str]:
|
||||
"""Generate cross-promotion strategy for personalized insights."""
|
||||
strategies = []
|
||||
|
||||
if 'video' in preferred_formats:
|
||||
strategies.extend([
|
||||
"Share video snippets on Instagram Stories",
|
||||
"Create YouTube Shorts from longer videos",
|
||||
"Cross-post video content to TikTok and Instagram Reels"
|
||||
])
|
||||
|
||||
if 'blog' in preferred_formats or 'article' in preferred_formats:
|
||||
strategies.extend([
|
||||
"Share blog excerpts on LinkedIn",
|
||||
"Create Twitter threads from blog posts",
|
||||
"Turn blog posts into video content"
|
||||
])
|
||||
|
||||
strategies.extend([
|
||||
"Use consistent hashtags across platforms",
|
||||
"Cross-promote content on different platforms",
|
||||
"Create platform-specific content variations",
|
||||
"Share behind-the-scenes content across all platforms"
|
||||
])
|
||||
|
||||
return strategies
|
||||
|
||||
def _generate_repurposing_plan(self, preferred_formats: list) -> Dict[str, List[str]]:
|
||||
"""Generate content repurposing plan for personalized insights."""
|
||||
repurposing_plan = {}
|
||||
|
||||
if 'video' in preferred_formats:
|
||||
repurposing_plan['video_content'] = [
|
||||
"Extract key quotes for social media posts",
|
||||
"Create blog posts from video transcripts",
|
||||
"Turn video clips into GIFs for social media",
|
||||
"Create podcast episodes from video content",
|
||||
"Extract audio for podcast distribution"
|
||||
]
|
||||
|
||||
if 'blog' in preferred_formats or 'article' in preferred_formats:
|
||||
repurposing_plan['written_content'] = [
|
||||
"Create social media posts from blog highlights",
|
||||
"Turn blog posts into video scripts",
|
||||
"Extract quotes for Twitter threads",
|
||||
"Create infographics from blog data",
|
||||
"Turn blog series into email courses"
|
||||
]
|
||||
|
||||
repurposing_plan['general'] = [
|
||||
"Repurpose top-performing content across platforms",
|
||||
"Create different formats for different audiences",
|
||||
"Update and republish evergreen content",
|
||||
"Combine multiple pieces into comprehensive guides",
|
||||
"Extract tips and insights for social media"
|
||||
]
|
||||
|
||||
return repurposing_plan
|
||||
|
||||
def analyze_performance_optimization(self, target_metrics: Dict, content_preferences: Dict, preferred_formats: list, team_size: int) -> Dict[str, Any]:
|
||||
"""Analyze content performance optimization for personalized insights."""
|
||||
optimization_strategies = []
|
||||
|
||||
# Content quality optimization
|
||||
optimization_strategies.append({
|
||||
"strategy": "Content Quality Optimization",
|
||||
"focus_area": "Engagement and retention",
|
||||
"tactics": [
|
||||
"Create content that solves specific problems",
|
||||
"Use storytelling to make content memorable",
|
||||
"Include clear calls-to-action in every piece",
|
||||
"Optimize content length for each platform",
|
||||
"Use data to identify top-performing content types"
|
||||
],
|
||||
"free_tools": ["Google Analytics", "Platform Insights", "A/B Testing"],
|
||||
"expected_improvement": "50% increase in engagement"
|
||||
})
|
||||
|
||||
# SEO optimization
|
||||
optimization_strategies.append({
|
||||
"strategy": "SEO and Discoverability",
|
||||
"focus_area": "Organic reach and traffic",
|
||||
"tactics": [
|
||||
"Research and target relevant keywords",
|
||||
"Optimize titles and descriptions",
|
||||
"Create evergreen content that ranks",
|
||||
"Build backlinks through guest posting",
|
||||
"Improve page load speed and mobile experience"
|
||||
],
|
||||
"free_tools": ["Google Keyword Planner", "Google Search Console", "Yoast SEO"],
|
||||
"expected_improvement": "100% increase in organic traffic"
|
||||
})
|
||||
|
||||
# Audience engagement optimization
|
||||
optimization_strategies.append({
|
||||
"strategy": "Audience Engagement",
|
||||
"focus_area": "Community building and loyalty",
|
||||
"tactics": [
|
||||
"Respond to every comment within 24 hours",
|
||||
"Create interactive content (polls, questions)",
|
||||
"Host live sessions and Q&As",
|
||||
"Share behind-the-scenes content",
|
||||
"Create exclusive content for engaged followers"
|
||||
],
|
||||
"free_tools": ["Instagram Stories", "Twitter Spaces", "YouTube Live"],
|
||||
"expected_improvement": "75% increase in community engagement"
|
||||
})
|
||||
|
||||
# Content distribution optimization
|
||||
optimization_strategies.append({
|
||||
"strategy": "Distribution Optimization",
|
||||
"focus_area": "Reach and visibility",
|
||||
"tactics": [
|
||||
"Post at optimal times for your audience",
|
||||
"Use platform-specific features (Stories, Reels, etc.)",
|
||||
"Cross-promote content across platforms",
|
||||
"Collaborate with other creators",
|
||||
"Participate in trending conversations"
|
||||
],
|
||||
"free_tools": ["Later", "Buffer", "Hootsuite"],
|
||||
"expected_improvement": "200% increase in reach"
|
||||
})
|
||||
|
||||
return {
|
||||
"optimization_strategies": optimization_strategies,
|
||||
"performance_tracking_metrics": [
|
||||
"Engagement rate (likes, comments, shares)",
|
||||
"Reach and impressions",
|
||||
"Click-through rates",
|
||||
"Time spent on content",
|
||||
"Follower growth rate",
|
||||
"Conversion rates (email signups, sales)"
|
||||
],
|
||||
"free_analytics_tools": [
|
||||
"Google Analytics (website traffic)",
|
||||
"Platform Insights (social media)",
|
||||
"Google Search Console (SEO)",
|
||||
"Email marketing analytics",
|
||||
"YouTube Analytics (video performance)"
|
||||
],
|
||||
"optimization_timeline": {
|
||||
"immediate": "Set up tracking and identify baseline metrics",
|
||||
"week_1": "Implement one optimization strategy",
|
||||
"month_1": "Analyze results and adjust strategy",
|
||||
"month_3": "Scale successful tactics and experiment with new ones"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
"""
|
||||
Prompt Engineering Service
|
||||
AI prompt creation and management.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
|
||||
# Import database models
|
||||
from models.enhanced_strategy_models import EnhancedContentStrategy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class PromptEngineeringService:
|
||||
"""Service for prompt engineering."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def create_specialized_prompt(self, strategy: EnhancedContentStrategy, analysis_type: str) -> str:
|
||||
"""Create specialized AI prompts for each analysis type."""
|
||||
|
||||
base_context = f"""
|
||||
Business Context:
|
||||
- Industry: {strategy.industry}
|
||||
- Business Objectives: {strategy.business_objectives}
|
||||
- Target Metrics: {strategy.target_metrics}
|
||||
- Content Budget: {strategy.content_budget}
|
||||
- Team Size: {strategy.team_size}
|
||||
- Implementation Timeline: {strategy.implementation_timeline}
|
||||
- Market Share: {strategy.market_share}
|
||||
- Competitive Position: {strategy.competitive_position}
|
||||
- Performance Metrics: {strategy.performance_metrics}
|
||||
|
||||
Audience Intelligence:
|
||||
- Content Preferences: {strategy.content_preferences}
|
||||
- Consumption Patterns: {strategy.consumption_patterns}
|
||||
- Audience Pain Points: {strategy.audience_pain_points}
|
||||
- Buying Journey: {strategy.buying_journey}
|
||||
- Seasonal Trends: {strategy.seasonal_trends}
|
||||
- Engagement Metrics: {strategy.engagement_metrics}
|
||||
|
||||
Competitive Intelligence:
|
||||
- Top Competitors: {strategy.top_competitors}
|
||||
- Competitor Content Strategies: {strategy.competitor_content_strategies}
|
||||
- Market Gaps: {strategy.market_gaps}
|
||||
- Industry Trends: {strategy.industry_trends}
|
||||
- Emerging Trends: {strategy.emerging_trends}
|
||||
|
||||
Content Strategy:
|
||||
- Preferred Formats: {strategy.preferred_formats}
|
||||
- Content Mix: {strategy.content_mix}
|
||||
- Content Frequency: {strategy.content_frequency}
|
||||
- Optimal Timing: {strategy.optimal_timing}
|
||||
- Quality Metrics: {strategy.quality_metrics}
|
||||
- Editorial Guidelines: {strategy.editorial_guidelines}
|
||||
- Brand Voice: {strategy.brand_voice}
|
||||
|
||||
Performance & Analytics:
|
||||
- Traffic Sources: {strategy.traffic_sources}
|
||||
- Conversion Rates: {strategy.conversion_rates}
|
||||
- Content ROI Targets: {strategy.content_roi_targets}
|
||||
- A/B Testing Capabilities: {strategy.ab_testing_capabilities}
|
||||
"""
|
||||
|
||||
specialized_prompts = {
|
||||
'comprehensive_strategy': f"""
|
||||
{base_context}
|
||||
|
||||
TASK: Generate a comprehensive content strategy analysis that provides:
|
||||
1. Strategic positioning and market analysis
|
||||
2. Audience targeting and persona development
|
||||
3. Content pillar recommendations with rationale
|
||||
4. Competitive advantage identification
|
||||
5. Performance optimization strategies
|
||||
6. Risk assessment and mitigation plans
|
||||
7. Implementation roadmap with milestones
|
||||
8. Success metrics and KPIs
|
||||
|
||||
REQUIREMENTS:
|
||||
- Provide actionable, specific recommendations
|
||||
- Include data-driven insights
|
||||
- Consider industry best practices
|
||||
- Address both short-term and long-term goals
|
||||
- Provide confidence levels for each recommendation
|
||||
""",
|
||||
|
||||
'audience_intelligence': f"""
|
||||
{base_context}
|
||||
|
||||
TASK: Generate detailed audience intelligence analysis including:
|
||||
1. Comprehensive audience persona development
|
||||
2. Content preference analysis and recommendations
|
||||
3. Consumption pattern insights and optimization
|
||||
4. Pain point identification and content solutions
|
||||
5. Buying journey mapping and content alignment
|
||||
6. Seasonal trend analysis and content planning
|
||||
7. Engagement pattern analysis and optimization
|
||||
8. Audience segmentation strategies
|
||||
|
||||
REQUIREMENTS:
|
||||
- Use data-driven insights from provided metrics
|
||||
- Provide specific content recommendations for each audience segment
|
||||
- Include engagement optimization strategies
|
||||
- Consider cultural and behavioral factors
|
||||
""",
|
||||
|
||||
'competitive_intelligence': f"""
|
||||
{base_context}
|
||||
|
||||
TASK: Generate comprehensive competitive intelligence analysis including:
|
||||
1. Competitor content strategy analysis
|
||||
2. Market gap identification and opportunities
|
||||
3. Competitive advantage development strategies
|
||||
4. Industry trend analysis and implications
|
||||
5. Emerging trend identification and early adoption strategies
|
||||
6. Competitive positioning recommendations
|
||||
7. Market opportunity assessment
|
||||
8. Competitive response strategies
|
||||
|
||||
REQUIREMENTS:
|
||||
- Analyze provided competitor data thoroughly
|
||||
- Identify unique market opportunities
|
||||
- Provide actionable competitive strategies
|
||||
- Consider both direct and indirect competitors
|
||||
""",
|
||||
|
||||
'performance_optimization': f"""
|
||||
{base_context}
|
||||
|
||||
TASK: Generate performance optimization analysis including:
|
||||
1. Current performance analysis and benchmarking
|
||||
2. Traffic source optimization strategies
|
||||
3. Conversion rate improvement recommendations
|
||||
4. Content ROI optimization strategies
|
||||
5. A/B testing framework and recommendations
|
||||
6. Performance monitoring and analytics setup
|
||||
7. Optimization roadmap and priorities
|
||||
8. Success metrics and tracking implementation
|
||||
|
||||
REQUIREMENTS:
|
||||
- Provide specific, measurable optimization strategies
|
||||
- Include data-driven recommendations
|
||||
- Consider both technical and content optimizations
|
||||
- Provide implementation timelines and priorities
|
||||
""",
|
||||
|
||||
'content_calendar_optimization': f"""
|
||||
{base_context}
|
||||
|
||||
TASK: Generate content calendar optimization analysis including:
|
||||
1. Optimal content frequency and timing analysis
|
||||
2. Content mix optimization and balance
|
||||
3. Seasonal content planning and scheduling
|
||||
4. Content pillar integration and scheduling
|
||||
5. Platform-specific content adaptation
|
||||
6. Content repurposing and amplification strategies
|
||||
7. Editorial calendar optimization
|
||||
8. Content performance tracking and adjustment
|
||||
|
||||
REQUIREMENTS:
|
||||
- Provide specific scheduling recommendations
|
||||
- Include content mix optimization strategies
|
||||
- Consider platform-specific requirements
|
||||
- Provide seasonal and trend-based planning
|
||||
"""
|
||||
}
|
||||
|
||||
return specialized_prompts.get(analysis_type, base_context)
|
||||
@@ -0,0 +1,205 @@
|
||||
"""
|
||||
Quality Validation Service
|
||||
AI response quality assessment and strategic analysis.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any, List
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class QualityValidationService:
|
||||
"""Service for quality validation and strategic analysis."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def validate_against_schema(self, data: Dict[str, Any], schema: Dict[str, Any]) -> None:
|
||||
"""Validate data against a minimal JSON-like schema definition.
|
||||
Raises ValueError on failure.
|
||||
Schema format example:
|
||||
{"type": "object", "required": ["strategy_brief", "channels"], "properties": {"strategy_brief": {"type": "object"}, "channels": {"type": "array"}}}
|
||||
"""
|
||||
def _check(node, sch, path="$"):
|
||||
t = sch.get("type")
|
||||
if t == "object":
|
||||
if not isinstance(node, dict):
|
||||
raise ValueError(f"Schema error at {path}: expected object")
|
||||
for req in sch.get("required", []):
|
||||
if req not in node or node[req] in (None, ""):
|
||||
raise ValueError(f"Schema error at {path}.{req}: required field missing")
|
||||
for key, sub in sch.get("properties", {}).items():
|
||||
if key in node:
|
||||
_check(node[key], sub, f"{path}.{key}")
|
||||
elif t == "array":
|
||||
if not isinstance(node, list):
|
||||
raise ValueError(f"Schema error at {path}: expected array")
|
||||
item_s = sch.get("items")
|
||||
if item_s:
|
||||
for i, item in enumerate(node):
|
||||
_check(item, item_s, f"{path}[{i}]")
|
||||
elif t == "string":
|
||||
if not isinstance(node, str) or not node.strip():
|
||||
raise ValueError(f"Schema error at {path}: expected non-empty string")
|
||||
elif t == "number":
|
||||
if not isinstance(node, (int, float)):
|
||||
raise ValueError(f"Schema error at {path}: expected number")
|
||||
elif t == "boolean":
|
||||
if not isinstance(node, bool):
|
||||
raise ValueError(f"Schema error at {path}: expected boolean")
|
||||
elif t == "any":
|
||||
return
|
||||
else:
|
||||
return
|
||||
_check(data, schema)
|
||||
|
||||
def calculate_strategic_scores(self, ai_recommendations: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""Calculate strategic performance scores from AI recommendations."""
|
||||
scores = {
|
||||
'overall_score': 0.0,
|
||||
'content_quality_score': 0.0,
|
||||
'engagement_score': 0.0,
|
||||
'conversion_score': 0.0,
|
||||
'innovation_score': 0.0
|
||||
}
|
||||
|
||||
# Calculate scores based on AI recommendations
|
||||
total_confidence = 0
|
||||
total_score = 0
|
||||
|
||||
for analysis_type, recommendations in ai_recommendations.items():
|
||||
if isinstance(recommendations, dict) and 'metrics' in recommendations:
|
||||
metrics = recommendations['metrics']
|
||||
score = metrics.get('score', 50)
|
||||
confidence = metrics.get('confidence', 0.5)
|
||||
|
||||
total_score += score * confidence
|
||||
total_confidence += confidence
|
||||
|
||||
if total_confidence > 0:
|
||||
scores['overall_score'] = total_score / total_confidence
|
||||
|
||||
# Set other scores based on overall score
|
||||
scores['content_quality_score'] = scores['overall_score'] * 1.1
|
||||
scores['engagement_score'] = scores['overall_score'] * 0.9
|
||||
scores['conversion_score'] = scores['overall_score'] * 0.95
|
||||
scores['innovation_score'] = scores['overall_score'] * 1.05
|
||||
|
||||
return scores
|
||||
|
||||
def extract_market_positioning(self, ai_recommendations: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Extract market positioning from AI recommendations."""
|
||||
return {
|
||||
'industry_position': 'emerging',
|
||||
'competitive_advantage': 'AI-powered content',
|
||||
'market_share': '2.5%',
|
||||
'positioning_score': 4
|
||||
}
|
||||
|
||||
def extract_competitive_advantages(self, ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Extract competitive advantages from AI recommendations."""
|
||||
return [
|
||||
{
|
||||
'advantage': 'AI-powered content creation',
|
||||
'impact': 'High',
|
||||
'implementation': 'In Progress'
|
||||
},
|
||||
{
|
||||
'advantage': 'Data-driven strategy',
|
||||
'impact': 'Medium',
|
||||
'implementation': 'Complete'
|
||||
}
|
||||
]
|
||||
|
||||
def extract_strategic_risks(self, ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Extract strategic risks from AI recommendations."""
|
||||
return [
|
||||
{
|
||||
'risk': 'Content saturation in market',
|
||||
'probability': 'Medium',
|
||||
'impact': 'High'
|
||||
},
|
||||
{
|
||||
'risk': 'Algorithm changes affecting reach',
|
||||
'probability': 'High',
|
||||
'impact': 'Medium'
|
||||
}
|
||||
]
|
||||
|
||||
def extract_opportunity_analysis(self, ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Extract opportunity analysis from AI recommendations."""
|
||||
return [
|
||||
{
|
||||
'opportunity': 'Video content expansion',
|
||||
'potential_impact': 'High',
|
||||
'implementation_ease': 'Medium'
|
||||
},
|
||||
{
|
||||
'opportunity': 'Social media engagement',
|
||||
'potential_impact': 'Medium',
|
||||
'implementation_ease': 'High'
|
||||
}
|
||||
]
|
||||
|
||||
def validate_ai_response_quality(self, ai_response: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate the quality of AI response."""
|
||||
quality_metrics = {
|
||||
'completeness': 0.0,
|
||||
'relevance': 0.0,
|
||||
'actionability': 0.0,
|
||||
'confidence': 0.0,
|
||||
'overall_quality': 0.0
|
||||
}
|
||||
|
||||
# Calculate completeness
|
||||
required_fields = ['recommendations', 'insights', 'metrics']
|
||||
present_fields = sum(1 for field in required_fields if field in ai_response)
|
||||
quality_metrics['completeness'] = present_fields / len(required_fields)
|
||||
|
||||
# Calculate relevance (placeholder logic)
|
||||
quality_metrics['relevance'] = 0.8 if ai_response.get('analysis_type') else 0.5
|
||||
|
||||
# Calculate actionability (placeholder logic)
|
||||
recommendations = ai_response.get('recommendations', [])
|
||||
quality_metrics['actionability'] = min(1.0, len(recommendations) / 5.0)
|
||||
|
||||
# Calculate confidence
|
||||
metrics = ai_response.get('metrics', {})
|
||||
quality_metrics['confidence'] = metrics.get('confidence', 0.5)
|
||||
|
||||
# Calculate overall quality
|
||||
quality_metrics['overall_quality'] = sum(quality_metrics.values()) / len(quality_metrics)
|
||||
|
||||
return quality_metrics
|
||||
|
||||
def assess_strategy_quality(self, strategy_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Assess the overall quality of a content strategy."""
|
||||
quality_assessment = {
|
||||
'data_completeness': 0.0,
|
||||
'strategic_clarity': 0.0,
|
||||
'implementation_readiness': 0.0,
|
||||
'competitive_positioning': 0.0,
|
||||
'overall_quality': 0.0
|
||||
}
|
||||
|
||||
# Assess data completeness
|
||||
required_fields = [
|
||||
'business_objectives', 'target_metrics', 'content_budget',
|
||||
'team_size', 'implementation_timeline'
|
||||
]
|
||||
present_fields = sum(1 for field in required_fields if strategy_data.get(field))
|
||||
quality_assessment['data_completeness'] = present_fields / len(required_fields)
|
||||
|
||||
# Assess strategic clarity (placeholder logic)
|
||||
quality_assessment['strategic_clarity'] = 0.7 if strategy_data.get('business_objectives') else 0.3
|
||||
|
||||
# Assess implementation readiness (placeholder logic)
|
||||
quality_assessment['implementation_readiness'] = 0.6 if strategy_data.get('team_size') else 0.2
|
||||
|
||||
# Assess competitive positioning (placeholder logic)
|
||||
quality_assessment['competitive_positioning'] = 0.5 if strategy_data.get('competitive_position') else 0.2
|
||||
|
||||
# Calculate overall quality
|
||||
quality_assessment['overall_quality'] = sum(quality_assessment.values()) / len(quality_assessment)
|
||||
|
||||
return quality_assessment
|
||||
@@ -0,0 +1,408 @@
|
||||
"""
|
||||
Strategic Intelligence Analyzer
|
||||
Handles comprehensive strategic intelligence analysis and generation.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class StrategicIntelligenceAnalyzer:
|
||||
"""Analyzes and generates comprehensive strategic intelligence."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def analyze_market_positioning(self, business_objectives: Dict, industry: str, content_preferences: Dict, team_size: int) -> Dict[str, Any]:
|
||||
"""Analyze market positioning for personalized insights."""
|
||||
# Calculate positioning score based on multiple factors
|
||||
score = 75 # Base score
|
||||
|
||||
# Adjust based on business objectives
|
||||
if business_objectives.get('brand_awareness'):
|
||||
score += 10
|
||||
if business_objectives.get('lead_generation'):
|
||||
score += 8
|
||||
if business_objectives.get('thought_leadership'):
|
||||
score += 12
|
||||
|
||||
# Adjust based on team size (solopreneurs get bonus for agility)
|
||||
if team_size <= 3:
|
||||
score += 8 # Solopreneurs are more agile
|
||||
elif team_size <= 10:
|
||||
score += 3
|
||||
|
||||
# Adjust based on content preferences
|
||||
if content_preferences.get('video_content'):
|
||||
score += 8
|
||||
if content_preferences.get('interactive_content'):
|
||||
score += 6
|
||||
|
||||
score = min(100, max(0, score))
|
||||
|
||||
return {
|
||||
"score": score,
|
||||
"strengths": [
|
||||
"Agile content production and quick pivots",
|
||||
"Direct connection with audience",
|
||||
"Authentic personal brand voice",
|
||||
"Cost-effective content creation",
|
||||
"Rapid experimentation capabilities"
|
||||
],
|
||||
"weaknesses": [
|
||||
"Limited content production capacity",
|
||||
"Time constraints for content creation",
|
||||
"Limited access to professional tools",
|
||||
"Need for content automation",
|
||||
"Limited reach without paid promotion"
|
||||
],
|
||||
"opportunities": [
|
||||
"Leverage personal brand authenticity",
|
||||
"Focus on niche content areas",
|
||||
"Build community-driven content",
|
||||
"Utilize free content creation tools",
|
||||
"Partner with other creators"
|
||||
],
|
||||
"threats": [
|
||||
"Content saturation in market",
|
||||
"Algorithm changes affecting reach",
|
||||
"Time constraints limiting output",
|
||||
"Competition from larger brands",
|
||||
"Platform dependency risks"
|
||||
]
|
||||
}
|
||||
|
||||
def identify_competitive_advantages(self, business_objectives: Dict, content_preferences: Dict, preferred_formats: list, team_size: int) -> List[Dict[str, Any]]:
|
||||
"""Identify competitive advantages for personalized insights."""
|
||||
try:
|
||||
advantages = []
|
||||
|
||||
# Analyze business objectives for competitive advantages
|
||||
if business_objectives.get('lead_generation'):
|
||||
advantages.append({
|
||||
"advantage": "Direct lead generation capabilities",
|
||||
"description": "Ability to create content that directly converts visitors to leads",
|
||||
"impact": "High",
|
||||
"implementation": "Focus on lead magnets and conversion-optimized content",
|
||||
"roi_potential": "300% return on investment",
|
||||
"differentiation": "Personal connection vs corporate approach"
|
||||
})
|
||||
|
||||
if business_objectives.get('brand_awareness'):
|
||||
advantages.append({
|
||||
"advantage": "Authentic personal brand voice",
|
||||
"description": "Unique personal perspective that builds trust and connection",
|
||||
"impact": "High",
|
||||
"implementation": "Share personal stories and behind-the-scenes content",
|
||||
"roi_potential": "250% return on investment",
|
||||
"differentiation": "Authenticity vs polished corporate messaging"
|
||||
})
|
||||
|
||||
if business_objectives.get('thought_leadership'):
|
||||
advantages.append({
|
||||
"advantage": "Niche expertise and authority",
|
||||
"description": "Deep knowledge in specific areas that positions you as the go-to expert",
|
||||
"impact": "Very High",
|
||||
"implementation": "Create comprehensive, educational content in your niche",
|
||||
"roi_potential": "400% return on investment",
|
||||
"differentiation": "Specialized expertise vs generalist approach"
|
||||
})
|
||||
|
||||
# Analyze content preferences for advantages
|
||||
if content_preferences.get('video_content'):
|
||||
advantages.append({
|
||||
"advantage": "Video content expertise",
|
||||
"description": "Ability to create engaging video content that drives higher engagement",
|
||||
"impact": "High",
|
||||
"implementation": "Focus on short-form video platforms (TikTok, Instagram Reels)",
|
||||
"roi_potential": "400% return on investment",
|
||||
"differentiation": "Visual storytelling vs text-only content"
|
||||
})
|
||||
|
||||
if content_preferences.get('interactive_content'):
|
||||
advantages.append({
|
||||
"advantage": "Interactive content capabilities",
|
||||
"description": "Ability to create content that engages and involves the audience",
|
||||
"impact": "Medium",
|
||||
"implementation": "Use polls, questions, and interactive elements",
|
||||
"roi_potential": "200% return on investment",
|
||||
"differentiation": "Two-way communication vs one-way broadcasting"
|
||||
})
|
||||
|
||||
# Analyze team size advantages
|
||||
if team_size == 1:
|
||||
advantages.append({
|
||||
"advantage": "Agility and quick pivots",
|
||||
"description": "Ability to respond quickly to trends and opportunities",
|
||||
"impact": "High",
|
||||
"implementation": "Stay current with trends and adapt content quickly",
|
||||
"roi_potential": "150% return on investment",
|
||||
"differentiation": "Speed vs corporate approval processes"
|
||||
})
|
||||
|
||||
# Analyze preferred formats for advantages
|
||||
if 'video' in preferred_formats:
|
||||
advantages.append({
|
||||
"advantage": "Multi-platform video presence",
|
||||
"description": "Ability to create video content for multiple platforms",
|
||||
"impact": "High",
|
||||
"implementation": "Repurpose video content across TikTok, Instagram, YouTube",
|
||||
"roi_potential": "350% return on investment",
|
||||
"differentiation": "Visual engagement vs static content"
|
||||
})
|
||||
|
||||
if 'blog' in preferred_formats or 'article' in preferred_formats:
|
||||
advantages.append({
|
||||
"advantage": "SEO-optimized content creation",
|
||||
"description": "Ability to create content that ranks well in search engines",
|
||||
"impact": "High",
|
||||
"implementation": "Focus on keyword research and SEO best practices",
|
||||
"roi_potential": "300% return on investment",
|
||||
"differentiation": "Organic reach vs paid advertising"
|
||||
})
|
||||
|
||||
# If no specific advantages found, provide general ones
|
||||
if not advantages:
|
||||
advantages = [
|
||||
{
|
||||
"advantage": "Personal connection and authenticity",
|
||||
"description": "Ability to build genuine relationships with your audience",
|
||||
"impact": "High",
|
||||
"implementation": "Share personal stories and be transparent",
|
||||
"roi_potential": "250% return on investment",
|
||||
"differentiation": "Authentic voice vs corporate messaging"
|
||||
},
|
||||
{
|
||||
"advantage": "Niche expertise",
|
||||
"description": "Deep knowledge in your specific area of expertise",
|
||||
"impact": "High",
|
||||
"implementation": "Focus on your unique knowledge and experience",
|
||||
"roi_potential": "300% return on investment",
|
||||
"differentiation": "Specialized knowledge vs generalist approach"
|
||||
}
|
||||
]
|
||||
|
||||
return advantages
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating competitive advantages: {str(e)}")
|
||||
raise Exception(f"Failed to generate competitive advantages: {str(e)}")
|
||||
|
||||
def assess_strategic_risks(self, industry: str, market_gaps: list, team_size: int, content_frequency: str) -> List[Dict[str, Any]]:
|
||||
"""Assess strategic risks for personalized insights."""
|
||||
risks = []
|
||||
|
||||
# Content saturation risk
|
||||
risks.append({
|
||||
"risk": "Content saturation in market",
|
||||
"probability": "Medium",
|
||||
"impact": "High",
|
||||
"mitigation": "Focus on unique personal perspective and niche topics",
|
||||
"monitoring": "Track content performance vs competitors, monitor engagement rates",
|
||||
"timeline": "Ongoing",
|
||||
"resources_needed": "Free competitive analysis tools"
|
||||
})
|
||||
|
||||
# Algorithm changes risk
|
||||
risks.append({
|
||||
"risk": "Algorithm changes affecting reach",
|
||||
"probability": "High",
|
||||
"impact": "Medium",
|
||||
"mitigation": "Diversify content formats and platforms, build owned audience",
|
||||
"monitoring": "Monitor platform algorithm updates, track reach changes",
|
||||
"timeline": "Ongoing",
|
||||
"resources_needed": "Free multi-platform strategy"
|
||||
})
|
||||
|
||||
# Time constraints risk
|
||||
if team_size == 1:
|
||||
risks.append({
|
||||
"risk": "Time constraints limiting content output",
|
||||
"probability": "High",
|
||||
"impact": "High",
|
||||
"mitigation": "Implement content batching, repurposing, and automation",
|
||||
"monitoring": "Track content creation time, monitor output consistency",
|
||||
"timeline": "1-2 months",
|
||||
"resources_needed": "Free content planning tools"
|
||||
})
|
||||
|
||||
# Platform dependency risk
|
||||
risks.append({
|
||||
"risk": "Platform dependency risks",
|
||||
"probability": "Medium",
|
||||
"impact": "Medium",
|
||||
"mitigation": "Build owned audience through email lists and personal websites",
|
||||
"monitoring": "Track platform-specific vs owned audience growth",
|
||||
"timeline": "3-6 months",
|
||||
"resources_needed": "Free email marketing tools"
|
||||
})
|
||||
|
||||
return risks
|
||||
|
||||
def analyze_opportunities(self, business_objectives: Dict, market_gaps: list, preferred_formats: list) -> List[Dict[str, Any]]:
|
||||
"""Analyze opportunities for personalized insights."""
|
||||
opportunities = []
|
||||
|
||||
# Video content opportunity
|
||||
if 'video' not in preferred_formats:
|
||||
opportunities.append({
|
||||
"opportunity": "Video content expansion",
|
||||
"potential_impact": "High",
|
||||
"implementation_ease": "Medium",
|
||||
"timeline": "1-2 months",
|
||||
"resource_requirements": "Free video tools (TikTok, Instagram Reels, YouTube Shorts)",
|
||||
"roi_potential": "400% return on investment",
|
||||
"description": "Video content generates 4x more engagement than text-only content"
|
||||
})
|
||||
|
||||
# Podcast opportunity
|
||||
opportunities.append({
|
||||
"opportunity": "Start a podcast",
|
||||
"potential_impact": "High",
|
||||
"implementation_ease": "Medium",
|
||||
"timeline": "2-3 months",
|
||||
"resource_requirements": "Free podcast hosting platforms",
|
||||
"roi_potential": "500% return on investment",
|
||||
"description": "Podcasts build deep audience relationships and establish thought leadership"
|
||||
})
|
||||
|
||||
# Newsletter opportunity
|
||||
opportunities.append({
|
||||
"opportunity": "Email newsletter",
|
||||
"potential_impact": "High",
|
||||
"implementation_ease": "High",
|
||||
"timeline": "1 month",
|
||||
"resource_requirements": "Free email marketing tools",
|
||||
"roi_potential": "600% return on investment",
|
||||
"description": "Direct email communication builds owned audience and drives conversions"
|
||||
})
|
||||
|
||||
# Market gap opportunities
|
||||
for gap in market_gaps[:3]: # Top 3 gaps
|
||||
opportunities.append({
|
||||
"opportunity": f"Address market gap: {gap}",
|
||||
"potential_impact": "High",
|
||||
"implementation_ease": "Medium",
|
||||
"timeline": "2-4 months",
|
||||
"resource_requirements": "Free content research and creation",
|
||||
"roi_potential": "300% return on investment",
|
||||
"description": f"Filling the {gap} gap positions you as the go-to expert"
|
||||
})
|
||||
|
||||
return opportunities
|
||||
|
||||
def calculate_performance_metrics(self, target_metrics: Dict, team_size: int) -> Dict[str, Any]:
|
||||
"""Calculate performance metrics for personalized insights."""
|
||||
# Base metrics
|
||||
content_quality_score = 8.5
|
||||
engagement_rate = 4.2
|
||||
conversion_rate = 2.8
|
||||
roi_per_content = 320
|
||||
brand_awareness_score = 7.8
|
||||
|
||||
# Adjust based on team size (solopreneurs get bonus for authenticity)
|
||||
if team_size == 1:
|
||||
content_quality_score += 0.5 # Authenticity bonus
|
||||
engagement_rate += 0.3 # Personal connection
|
||||
elif team_size <= 3:
|
||||
content_quality_score += 0.2
|
||||
engagement_rate += 0.1
|
||||
|
||||
return {
|
||||
"content_quality_score": round(content_quality_score, 1),
|
||||
"engagement_rate": round(engagement_rate, 1),
|
||||
"conversion_rate": round(conversion_rate, 1),
|
||||
"roi_per_content": round(roi_per_content, 0),
|
||||
"brand_awareness_score": round(brand_awareness_score, 1),
|
||||
"content_efficiency": round(roi_per_content / 100 * 100, 1), # Normalized for solopreneurs
|
||||
"personal_brand_strength": round(brand_awareness_score * 1.2, 1) # Personal brand metric
|
||||
}
|
||||
|
||||
def generate_solopreneur_recommendations(self, business_objectives: Dict, team_size: int, preferred_formats: list, industry: str) -> List[Dict[str, Any]]:
|
||||
"""Generate personalized recommendations based on user data."""
|
||||
recommendations = []
|
||||
|
||||
# High priority recommendations
|
||||
if 'video' not in preferred_formats:
|
||||
recommendations.append({
|
||||
"priority": "High",
|
||||
"action": "Start creating short-form video content",
|
||||
"impact": "Increase engagement by 400% and reach by 300%",
|
||||
"timeline": "1 month",
|
||||
"resources_needed": "Free - use TikTok, Instagram Reels, YouTube Shorts",
|
||||
"roi_estimate": "400% return on investment",
|
||||
"implementation_steps": [
|
||||
"Download TikTok and Instagram apps",
|
||||
"Study trending content in your niche",
|
||||
"Create 3-5 short videos per week",
|
||||
"Engage with comments and build community"
|
||||
]
|
||||
})
|
||||
|
||||
# Email list building
|
||||
recommendations.append({
|
||||
"priority": "High",
|
||||
"action": "Build an email list",
|
||||
"impact": "Create owned audience, increase conversions by 200%",
|
||||
"timeline": "2 months",
|
||||
"resources_needed": "Free - use Mailchimp or ConvertKit free tier",
|
||||
"roi_estimate": "600% return on investment",
|
||||
"implementation_steps": [
|
||||
"Sign up for free email marketing tool",
|
||||
"Create lead magnet (free guide, checklist)",
|
||||
"Add signup forms to your content",
|
||||
"Send weekly valuable emails"
|
||||
]
|
||||
})
|
||||
|
||||
# Content batching
|
||||
if team_size == 1:
|
||||
recommendations.append({
|
||||
"priority": "High",
|
||||
"action": "Implement content batching",
|
||||
"impact": "Save 10 hours per week, increase output by 300%",
|
||||
"timeline": "2 weeks",
|
||||
"resources_needed": "Free - use Google Calendar and Notion",
|
||||
"roi_estimate": "300% return on investment",
|
||||
"implementation_steps": [
|
||||
"Block 4-hour content creation sessions",
|
||||
"Create content themes for each month",
|
||||
"Batch similar content types together",
|
||||
"Schedule content in advance"
|
||||
]
|
||||
})
|
||||
|
||||
# Medium priority recommendations
|
||||
recommendations.append({
|
||||
"priority": "Medium",
|
||||
"action": "Optimize for search engines",
|
||||
"impact": "Increase organic traffic by 200%",
|
||||
"timeline": "2 months",
|
||||
"resources_needed": "Free - use Google Keyword Planner",
|
||||
"roi_estimate": "200% return on investment",
|
||||
"implementation_steps": [
|
||||
"Research keywords in your niche",
|
||||
"Optimize existing content for target keywords",
|
||||
"Create SEO-optimized content calendar",
|
||||
"Monitor search rankings"
|
||||
]
|
||||
})
|
||||
|
||||
# Community building
|
||||
recommendations.append({
|
||||
"priority": "Medium",
|
||||
"action": "Build community engagement",
|
||||
"impact": "Increase loyalty and word-of-mouth by 150%",
|
||||
"timeline": "3 months",
|
||||
"resources_needed": "Free - use existing social platforms",
|
||||
"roi_estimate": "150% return on investment",
|
||||
"implementation_steps": [
|
||||
"Respond to every comment and message",
|
||||
"Create community challenges or contests",
|
||||
"Host live Q&A sessions",
|
||||
"Collaborate with other creators"
|
||||
]
|
||||
})
|
||||
|
||||
return recommendations
|
||||
@@ -0,0 +1,629 @@
|
||||
"""
|
||||
Strategy analyzer for AI-powered content strategy recommendations.
|
||||
Provides comprehensive AI analysis functions for content strategy generation,
|
||||
including specialized prompts, response parsing, and recommendation processing.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from models.enhanced_strategy_models import EnhancedContentStrategy, EnhancedAIAnalysisResult
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StrategyAnalyzer:
|
||||
"""AI-powered strategy analyzer for content strategy recommendations."""
|
||||
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
# Performance optimization settings
|
||||
self.prompt_versions = {
|
||||
'comprehensive_strategy': 'v2.1',
|
||||
'audience_intelligence': 'v2.0',
|
||||
'competitive_intelligence': 'v2.0',
|
||||
'performance_optimization': 'v2.1',
|
||||
'content_calendar_optimization': 'v2.0'
|
||||
}
|
||||
|
||||
self.quality_thresholds = {
|
||||
'min_confidence': 0.7,
|
||||
'min_completeness': 0.8,
|
||||
'max_response_time': 30.0 # seconds
|
||||
}
|
||||
|
||||
async def generate_comprehensive_ai_recommendations(self, strategy: EnhancedContentStrategy, db: Session) -> None:
|
||||
"""
|
||||
Generate comprehensive AI recommendations using 5 specialized prompts.
|
||||
|
||||
Args:
|
||||
strategy: The enhanced content strategy object
|
||||
db: Database session
|
||||
"""
|
||||
try:
|
||||
self.logger.info(f"Generating comprehensive AI recommendations for strategy: {strategy.id}")
|
||||
|
||||
start_time = datetime.utcnow()
|
||||
|
||||
# Generate recommendations for each analysis type
|
||||
analysis_types = [
|
||||
'comprehensive_strategy',
|
||||
'audience_intelligence',
|
||||
'competitive_intelligence',
|
||||
'performance_optimization',
|
||||
'content_calendar_optimization'
|
||||
]
|
||||
|
||||
ai_recommendations = {}
|
||||
successful_analyses = 0
|
||||
failed_analyses = 0
|
||||
|
||||
for analysis_type in analysis_types:
|
||||
try:
|
||||
# Generate recommendations without timeout (allow natural processing time)
|
||||
recommendations = await self.generate_specialized_recommendations(strategy, analysis_type, db)
|
||||
|
||||
# Validate recommendations before storing
|
||||
if recommendations and (recommendations.get('recommendations') or recommendations.get('insights')):
|
||||
ai_recommendations[analysis_type] = recommendations
|
||||
successful_analyses += 1
|
||||
|
||||
# Store individual analysis result
|
||||
analysis_result = EnhancedAIAnalysisResult(
|
||||
user_id=strategy.user_id,
|
||||
strategy_id=strategy.id,
|
||||
analysis_type=analysis_type,
|
||||
comprehensive_insights=recommendations.get('comprehensive_insights'),
|
||||
audience_intelligence=recommendations.get('audience_intelligence'),
|
||||
competitive_intelligence=recommendations.get('competitive_intelligence'),
|
||||
performance_optimization=recommendations.get('performance_optimization'),
|
||||
content_calendar_optimization=recommendations.get('content_calendar_optimization'),
|
||||
onboarding_data_used=strategy.onboarding_data_used,
|
||||
processing_time=(datetime.utcnow() - start_time).total_seconds(),
|
||||
ai_service_status="operational"
|
||||
)
|
||||
|
||||
db.add(analysis_result)
|
||||
else:
|
||||
self.logger.warning(f"Empty or invalid recommendations for {analysis_type}")
|
||||
failed_analyses += 1
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error generating {analysis_type} recommendations: {str(e)}")
|
||||
failed_analyses += 1
|
||||
continue
|
||||
|
||||
# Only commit if we have at least one successful analysis
|
||||
if successful_analyses > 0:
|
||||
db.commit()
|
||||
|
||||
# Update strategy with comprehensive AI analysis
|
||||
strategy.comprehensive_ai_analysis = ai_recommendations
|
||||
|
||||
# Import strategy utilities for scoring and analysis
|
||||
from ..utils.strategy_utils import (
|
||||
calculate_strategic_scores,
|
||||
extract_market_positioning,
|
||||
extract_competitive_advantages,
|
||||
extract_strategic_risks,
|
||||
extract_opportunity_analysis
|
||||
)
|
||||
|
||||
strategy.strategic_scores = calculate_strategic_scores(ai_recommendations)
|
||||
strategy.market_positioning = extract_market_positioning(ai_recommendations)
|
||||
strategy.competitive_advantages = extract_competitive_advantages(ai_recommendations)
|
||||
strategy.strategic_risks = extract_strategic_risks(ai_recommendations)
|
||||
strategy.opportunity_analysis = extract_opportunity_analysis(ai_recommendations)
|
||||
|
||||
db.commit()
|
||||
|
||||
processing_time = (datetime.utcnow() - start_time).total_seconds()
|
||||
self.logger.info(f"Comprehensive AI recommendations generated in {processing_time:.2f} seconds - {successful_analyses} successful, {failed_analyses} failed")
|
||||
else:
|
||||
self.logger.error("No successful AI analyses generated - strategy creation will continue without AI recommendations")
|
||||
# Don't raise error, allow strategy creation to continue without AI recommendations
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error generating comprehensive AI recommendations: {str(e)}")
|
||||
# Don't raise error, just log it as this is enhancement, not core functionality
|
||||
|
||||
async def generate_specialized_recommendations(self, strategy: EnhancedContentStrategy, analysis_type: str, db: Session) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate specialized recommendations using specific AI prompts.
|
||||
|
||||
Args:
|
||||
strategy: The enhanced content strategy object
|
||||
analysis_type: Type of analysis to perform
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
Dictionary with structured AI recommendations
|
||||
"""
|
||||
try:
|
||||
# Prepare strategy data for AI analysis
|
||||
strategy_data = strategy.to_dict()
|
||||
|
||||
# Get onboarding data for context
|
||||
onboarding_integration = await self.get_onboarding_integration(strategy.id, db)
|
||||
|
||||
# Create prompt based on analysis type
|
||||
prompt = self.create_specialized_prompt(strategy, analysis_type)
|
||||
|
||||
# Generate AI response (placeholder - integrate with actual AI service)
|
||||
ai_response = await self.call_ai_service(prompt, analysis_type)
|
||||
|
||||
# Parse and structure the response
|
||||
structured_response = self.parse_ai_response(ai_response, analysis_type)
|
||||
|
||||
return structured_response
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error generating {analysis_type} recommendations: {str(e)}")
|
||||
raise
|
||||
|
||||
def create_specialized_prompt(self, strategy: EnhancedContentStrategy, analysis_type: str) -> str:
|
||||
"""
|
||||
Create specialized AI prompts for each analysis type.
|
||||
|
||||
Args:
|
||||
strategy: The enhanced content strategy object
|
||||
analysis_type: Type of analysis to perform
|
||||
|
||||
Returns:
|
||||
Specialized prompt string for AI analysis
|
||||
"""
|
||||
|
||||
base_context = f"""
|
||||
Business Context:
|
||||
- Industry: {strategy.industry}
|
||||
- Business Objectives: {strategy.business_objectives}
|
||||
- Target Metrics: {strategy.target_metrics}
|
||||
- Content Budget: {strategy.content_budget}
|
||||
- Team Size: {strategy.team_size}
|
||||
- Implementation Timeline: {strategy.implementation_timeline}
|
||||
- Market Share: {strategy.market_share}
|
||||
- Competitive Position: {strategy.competitive_position}
|
||||
- Performance Metrics: {strategy.performance_metrics}
|
||||
|
||||
Audience Intelligence:
|
||||
- Content Preferences: {strategy.content_preferences}
|
||||
- Consumption Patterns: {strategy.consumption_patterns}
|
||||
- Audience Pain Points: {strategy.audience_pain_points}
|
||||
- Buying Journey: {strategy.buying_journey}
|
||||
- Seasonal Trends: {strategy.seasonal_trends}
|
||||
- Engagement Metrics: {strategy.engagement_metrics}
|
||||
|
||||
Competitive Intelligence:
|
||||
- Top Competitors: {strategy.top_competitors}
|
||||
- Competitor Content Strategies: {strategy.competitor_content_strategies}
|
||||
- Market Gaps: {strategy.market_gaps}
|
||||
- Industry Trends: {strategy.industry_trends}
|
||||
- Emerging Trends: {strategy.emerging_trends}
|
||||
|
||||
Content Strategy:
|
||||
- Preferred Formats: {strategy.preferred_formats}
|
||||
- Content Mix: {strategy.content_mix}
|
||||
- Content Frequency: {strategy.content_frequency}
|
||||
- Optimal Timing: {strategy.optimal_timing}
|
||||
- Quality Metrics: {strategy.quality_metrics}
|
||||
- Editorial Guidelines: {strategy.editorial_guidelines}
|
||||
- Brand Voice: {strategy.brand_voice}
|
||||
|
||||
Performance & Analytics:
|
||||
- Traffic Sources: {strategy.traffic_sources}
|
||||
- Conversion Rates: {strategy.conversion_rates}
|
||||
- Content ROI Targets: {strategy.content_roi_targets}
|
||||
- A/B Testing Capabilities: {strategy.ab_testing_capabilities}
|
||||
"""
|
||||
|
||||
specialized_prompts = {
|
||||
'comprehensive_strategy': f"""
|
||||
{base_context}
|
||||
|
||||
TASK: Generate a comprehensive content strategy analysis that provides:
|
||||
1. Strategic positioning and market analysis
|
||||
2. Audience targeting and persona development
|
||||
3. Content pillar recommendations with rationale
|
||||
4. Competitive advantage identification
|
||||
5. Performance optimization strategies
|
||||
6. Risk assessment and mitigation plans
|
||||
7. Implementation roadmap with milestones
|
||||
8. Success metrics and KPIs
|
||||
|
||||
REQUIREMENTS:
|
||||
- Provide actionable, specific recommendations
|
||||
- Include data-driven insights
|
||||
- Consider industry best practices
|
||||
- Address both short-term and long-term goals
|
||||
- Provide confidence levels for each recommendation
|
||||
""",
|
||||
|
||||
'audience_intelligence': f"""
|
||||
{base_context}
|
||||
|
||||
TASK: Generate detailed audience intelligence analysis including:
|
||||
1. Comprehensive audience persona development
|
||||
2. Content preference analysis and recommendations
|
||||
3. Consumption pattern insights and optimization
|
||||
4. Pain point identification and content solutions
|
||||
5. Buying journey mapping and content alignment
|
||||
6. Seasonal trend analysis and content planning
|
||||
7. Engagement pattern analysis and optimization
|
||||
8. Audience segmentation strategies
|
||||
|
||||
REQUIREMENTS:
|
||||
- Use data-driven insights from provided metrics
|
||||
- Provide specific content recommendations for each audience segment
|
||||
- Include engagement optimization strategies
|
||||
- Consider cultural and behavioral factors
|
||||
""",
|
||||
|
||||
'competitive_intelligence': f"""
|
||||
{base_context}
|
||||
|
||||
TASK: Generate comprehensive competitive intelligence analysis including:
|
||||
1. Competitor content strategy analysis
|
||||
2. Market gap identification and opportunities
|
||||
3. Competitive advantage development strategies
|
||||
4. Industry trend analysis and implications
|
||||
5. Emerging trend identification and early adoption strategies
|
||||
6. Competitive positioning recommendations
|
||||
7. Market opportunity assessment
|
||||
8. Competitive response strategies
|
||||
|
||||
REQUIREMENTS:
|
||||
- Analyze provided competitor data thoroughly
|
||||
- Identify unique market opportunities
|
||||
- Provide actionable competitive strategies
|
||||
- Consider both direct and indirect competitors
|
||||
""",
|
||||
|
||||
'performance_optimization': f"""
|
||||
{base_context}
|
||||
|
||||
TASK: Generate performance optimization analysis including:
|
||||
1. Current performance analysis and benchmarking
|
||||
2. Traffic source optimization strategies
|
||||
3. Conversion rate improvement recommendations
|
||||
4. Content ROI optimization strategies
|
||||
5. A/B testing framework and recommendations
|
||||
6. Performance monitoring and analytics setup
|
||||
7. Optimization roadmap and priorities
|
||||
8. Success metrics and tracking implementation
|
||||
|
||||
REQUIREMENTS:
|
||||
- Provide specific, measurable optimization strategies
|
||||
- Include data-driven recommendations
|
||||
- Consider both technical and content optimizations
|
||||
- Provide implementation timelines and priorities
|
||||
""",
|
||||
|
||||
'content_calendar_optimization': f"""
|
||||
{base_context}
|
||||
|
||||
TASK: Generate content calendar optimization analysis including:
|
||||
1. Optimal content frequency and timing analysis
|
||||
2. Content mix optimization and balance
|
||||
3. Seasonal content planning and scheduling
|
||||
4. Content pillar integration and scheduling
|
||||
5. Platform-specific content adaptation
|
||||
6. Content repurposing and amplification strategies
|
||||
7. Editorial calendar optimization
|
||||
8. Content performance tracking and adjustment
|
||||
|
||||
REQUIREMENTS:
|
||||
- Provide specific scheduling recommendations
|
||||
- Include content mix optimization strategies
|
||||
- Consider platform-specific requirements
|
||||
- Provide seasonal and trend-based planning
|
||||
"""
|
||||
}
|
||||
|
||||
return specialized_prompts.get(analysis_type, base_context)
|
||||
|
||||
async def call_ai_service(self, prompt: str, analysis_type: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Call AI service to generate recommendations.
|
||||
|
||||
Args:
|
||||
prompt: The AI prompt to send
|
||||
analysis_type: Type of analysis being performed
|
||||
|
||||
Returns:
|
||||
Dictionary with AI response
|
||||
|
||||
Raises:
|
||||
RuntimeError: If AI service is not available or fails
|
||||
"""
|
||||
try:
|
||||
# Import AI service manager
|
||||
from services.ai_service_manager import AIServiceManager, AIServiceType
|
||||
|
||||
# Initialize AI service
|
||||
ai_service = AIServiceManager()
|
||||
|
||||
# Map analysis types to AI service types
|
||||
service_type_mapping = {
|
||||
'comprehensive_strategy': AIServiceType.STRATEGIC_INTELLIGENCE,
|
||||
'audience_intelligence': AIServiceType.STRATEGIC_INTELLIGENCE,
|
||||
'competitive_intelligence': AIServiceType.MARKET_POSITION_ANALYSIS,
|
||||
'performance_optimization': AIServiceType.PERFORMANCE_PREDICTION,
|
||||
'content_calendar_optimization': AIServiceType.CONTENT_SCHEDULE_GENERATION
|
||||
}
|
||||
|
||||
# Get the appropriate service type, default to strategic intelligence
|
||||
service_type = service_type_mapping.get(analysis_type, AIServiceType.STRATEGIC_INTELLIGENCE)
|
||||
|
||||
# Define schema for AI response
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"recommendations": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"type": "string"},
|
||||
"description": {"type": "string"},
|
||||
"priority": {"type": "string"},
|
||||
"impact": {"type": "string"},
|
||||
"implementation_difficulty": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"insights": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"insight": {"type": "string"},
|
||||
"confidence": {"type": "string"},
|
||||
"data_support": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"confidence": {"type": "number"},
|
||||
"completeness": {"type": "number"},
|
||||
"actionability": {"type": "number"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Generate AI response using the service manager
|
||||
response = await ai_service.execute_structured_json_call(
|
||||
service_type,
|
||||
prompt,
|
||||
schema
|
||||
)
|
||||
|
||||
# Validate that we got actual AI response
|
||||
if not response:
|
||||
raise RuntimeError(f"AI service returned null response for {analysis_type}")
|
||||
|
||||
# Check for error in response
|
||||
if response.get("error"):
|
||||
error_msg = response.get("error", "Unknown error")
|
||||
if "Failed to parse JSON" in error_msg:
|
||||
# Try to extract partial data from raw response
|
||||
raw_response = response.get("raw_response", "")
|
||||
if raw_response:
|
||||
self.logger.warning(f"JSON parsing failed for {analysis_type}, attempting to extract partial data")
|
||||
partial_data = self._extract_partial_data_from_raw(raw_response)
|
||||
if partial_data:
|
||||
self.logger.info(f"Successfully extracted partial data for {analysis_type}")
|
||||
return partial_data
|
||||
|
||||
raise RuntimeError(f"AI service error for {analysis_type}: {error_msg}")
|
||||
|
||||
# Check if response has data
|
||||
if not response.get("data"):
|
||||
# Check if response itself contains the expected structure
|
||||
if response.get("recommendations") or response.get("insights"):
|
||||
self.logger.info(f"Using direct response structure for {analysis_type}")
|
||||
return response
|
||||
else:
|
||||
raise RuntimeError(f"AI service returned empty data for {analysis_type}")
|
||||
|
||||
# Return the structured response
|
||||
return response.get("data", {})
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"AI service failed for {analysis_type}: {str(e)}")
|
||||
raise RuntimeError(f"AI service integration failed for {analysis_type}: {str(e)}")
|
||||
|
||||
def _extract_partial_data_from_raw(self, raw_response: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Extract partial data from raw AI response when JSON parsing fails.
|
||||
"""
|
||||
try:
|
||||
# Look for common patterns in the raw response
|
||||
import re
|
||||
|
||||
# Extract recommendations
|
||||
recommendations = []
|
||||
rec_pattern = r'"title"\s*:\s*"([^"]+)"[^}]*"description"\s*:\s*"([^"]*)"'
|
||||
rec_matches = re.findall(rec_pattern, raw_response)
|
||||
for title, description in rec_matches:
|
||||
recommendations.append({
|
||||
"title": title,
|
||||
"description": description,
|
||||
"priority": "medium",
|
||||
"impact": "moderate",
|
||||
"implementation_difficulty": "medium"
|
||||
})
|
||||
|
||||
# Extract insights
|
||||
insights = []
|
||||
insight_pattern = r'"insight"\s*:\s*"([^"]+)"'
|
||||
insight_matches = re.findall(insight_pattern, raw_response)
|
||||
for insight in insight_matches:
|
||||
insights.append({
|
||||
"insight": insight,
|
||||
"confidence": "medium",
|
||||
"data_support": "industry_analysis"
|
||||
})
|
||||
|
||||
if recommendations or insights:
|
||||
return {
|
||||
"recommendations": recommendations,
|
||||
"insights": insights,
|
||||
"metrics": {
|
||||
"confidence": 0.6,
|
||||
"completeness": 0.5,
|
||||
"actionability": 0.7
|
||||
}
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Error extracting partial data: {e}")
|
||||
return None
|
||||
|
||||
def parse_ai_response(self, ai_response: Dict[str, Any], analysis_type: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Parse and structure AI response.
|
||||
|
||||
Args:
|
||||
ai_response: Raw AI response
|
||||
analysis_type: Type of analysis performed
|
||||
|
||||
Returns:
|
||||
Structured response dictionary
|
||||
|
||||
Raises:
|
||||
RuntimeError: If AI response is invalid or empty
|
||||
"""
|
||||
if not ai_response:
|
||||
raise RuntimeError(f"Empty AI response received for {analysis_type}")
|
||||
|
||||
# Validate that we have actual recommendations
|
||||
recommendations = ai_response.get('recommendations', [])
|
||||
insights = ai_response.get('insights', [])
|
||||
|
||||
if not recommendations and not insights:
|
||||
raise RuntimeError(f"No recommendations or insights found in AI response for {analysis_type}")
|
||||
|
||||
return {
|
||||
'analysis_type': analysis_type,
|
||||
'recommendations': recommendations,
|
||||
'insights': insights,
|
||||
'metrics': ai_response.get('metrics', {}),
|
||||
'confidence_score': ai_response.get('metrics', {}).get('confidence', 0.8)
|
||||
}
|
||||
|
||||
def get_fallback_recommendations(self, analysis_type: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Get fallback recommendations - DISABLED.
|
||||
|
||||
Args:
|
||||
analysis_type: Type of analysis
|
||||
|
||||
Returns:
|
||||
Never returns - always raises error
|
||||
|
||||
Raises:
|
||||
RuntimeError: Always raised as fallbacks are disabled
|
||||
"""
|
||||
raise RuntimeError(f"Fallback recommendations are disabled for {analysis_type}. Real AI insights required.")
|
||||
|
||||
async def get_latest_ai_analysis(self, strategy_id: int, db: Session) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get the latest AI analysis for a strategy.
|
||||
|
||||
Args:
|
||||
strategy_id: The strategy ID
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
Latest AI analysis result or None
|
||||
"""
|
||||
try:
|
||||
analysis = db.query(EnhancedAIAnalysisResult).filter(
|
||||
EnhancedAIAnalysisResult.strategy_id == strategy_id
|
||||
).order_by(EnhancedAIAnalysisResult.created_at.desc()).first()
|
||||
|
||||
return analysis.to_dict() if analysis else None
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting latest AI analysis: {str(e)}")
|
||||
return None
|
||||
|
||||
async def get_onboarding_integration(self, strategy_id: int, db: Session) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get onboarding data integration for a strategy.
|
||||
|
||||
Args:
|
||||
strategy_id: The strategy ID
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
Onboarding integration data or None
|
||||
"""
|
||||
try:
|
||||
from models.enhanced_strategy_models import OnboardingDataIntegration
|
||||
integration = db.query(OnboardingDataIntegration).filter(
|
||||
OnboardingDataIntegration.strategy_id == strategy_id
|
||||
).first()
|
||||
|
||||
return integration.to_dict() if integration else None
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting onboarding integration: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
# Standalone functions for backward compatibility
|
||||
async def generate_comprehensive_ai_recommendations(strategy: EnhancedContentStrategy, db: Session) -> None:
|
||||
"""Generate comprehensive AI recommendations using 5 specialized prompts."""
|
||||
analyzer = StrategyAnalyzer()
|
||||
return await analyzer.generate_comprehensive_ai_recommendations(strategy, db)
|
||||
|
||||
|
||||
async def generate_specialized_recommendations(strategy: EnhancedContentStrategy, analysis_type: str, db: Session) -> Dict[str, Any]:
|
||||
"""Generate specialized recommendations using specific AI prompts."""
|
||||
analyzer = StrategyAnalyzer()
|
||||
return await analyzer.generate_specialized_recommendations(strategy, analysis_type, db)
|
||||
|
||||
|
||||
def create_specialized_prompt(strategy: EnhancedContentStrategy, analysis_type: str) -> str:
|
||||
"""Create specialized AI prompts for each analysis type."""
|
||||
analyzer = StrategyAnalyzer()
|
||||
return analyzer.create_specialized_prompt(strategy, analysis_type)
|
||||
|
||||
|
||||
async def call_ai_service(prompt: str, analysis_type: str) -> Dict[str, Any]:
|
||||
"""Call AI service to generate recommendations."""
|
||||
analyzer = StrategyAnalyzer()
|
||||
return await analyzer.call_ai_service(prompt, analysis_type)
|
||||
|
||||
|
||||
def parse_ai_response(ai_response: Dict[str, Any], analysis_type: str) -> Dict[str, Any]:
|
||||
"""Parse and structure AI response."""
|
||||
analyzer = StrategyAnalyzer()
|
||||
return analyzer.parse_ai_response(ai_response, analysis_type)
|
||||
|
||||
|
||||
def get_fallback_recommendations(analysis_type: str) -> Dict[str, Any]:
|
||||
"""Get fallback recommendations (disabled)."""
|
||||
analyzer = StrategyAnalyzer()
|
||||
return analyzer.get_fallback_recommendations(analysis_type)
|
||||
|
||||
|
||||
async def get_latest_ai_analysis(strategy_id: int, db: Session) -> Optional[Dict[str, Any]]:
|
||||
"""Get the latest AI analysis for a strategy."""
|
||||
analyzer = StrategyAnalyzer()
|
||||
return await analyzer.get_latest_ai_analysis(strategy_id, db)
|
||||
|
||||
|
||||
async def get_onboarding_integration(strategy_id: int, db: Session) -> Optional[Dict[str, Any]]:
|
||||
"""Get onboarding data integration for a strategy."""
|
||||
analyzer = StrategyAnalyzer()
|
||||
return await analyzer.get_onboarding_integration(strategy_id, db)
|
||||
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
AI Generation Module
|
||||
AI-powered content strategy generation with comprehensive insights and recommendations.
|
||||
"""
|
||||
|
||||
from .strategy_generator import AIStrategyGenerator, StrategyGenerationConfig
|
||||
|
||||
__all__ = ["AIStrategyGenerator", "StrategyGenerationConfig"]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
# Dedicated auto-fill package for Content Strategy Builder inputs
|
||||
# Exposes AutoFillService for orchestrating onboarding data → normalized → transformed → frontend fields
|
||||
|
||||
from .autofill_service import AutoFillService
|
||||
@@ -0,0 +1,318 @@
|
||||
from typing import Any, Dict, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
from .autofill_service import AutoFillService
|
||||
from ...ai_analytics_service import ContentPlanningAIAnalyticsService
|
||||
from .ai_structured_autofill import AIStructuredAutofillService
|
||||
from .transparency_service import AutofillTransparencyService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class AutoFillRefreshService:
|
||||
"""Generates a fresh auto-fill payload for the Strategy Builder.
|
||||
This service does NOT persist anything. Intended for refresh flows.
|
||||
"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
self.autofill = AutoFillService(db)
|
||||
self.ai_analytics = ContentPlanningAIAnalyticsService()
|
||||
self.structured_ai = AIStructuredAutofillService()
|
||||
self.transparency = AutofillTransparencyService(db)
|
||||
|
||||
async def build_fresh_payload(self, user_id: int, use_ai: bool = True, ai_only: bool = False) -> Dict[str, Any]:
|
||||
"""Build a fresh auto-fill payload.
|
||||
- Reads latest onboarding-integrated data
|
||||
- Optionally augments with AI overrides (hook, not persisted)
|
||||
- Returns payload in the same shape as AutoFillService.get_autofill, plus meta
|
||||
"""
|
||||
logger.info(f"AutoFillRefreshService: starting build_fresh_payload | user=%s | use_ai=%s | ai_only=%s", user_id, use_ai, ai_only)
|
||||
|
||||
# Base context from onboarding analysis (used for AI context only when ai_only)
|
||||
logger.debug("AutoFillRefreshService: processing onboarding context | user=%s", user_id)
|
||||
base_context = await self.autofill.integration.process_onboarding_data(user_id, self.db)
|
||||
logger.debug(
|
||||
"AutoFillRefreshService: context keys=%s | website=%s research=%s api=%s session=%s",
|
||||
list(base_context.keys()) if isinstance(base_context, dict) else 'n/a',
|
||||
bool((base_context or {}).get('website_analysis')),
|
||||
bool((base_context or {}).get('research_preferences')),
|
||||
bool((base_context or {}).get('api_keys_data')),
|
||||
bool((base_context or {}).get('onboarding_session')),
|
||||
)
|
||||
|
||||
# Log detailed context analysis
|
||||
logger.info(f"AutoFillRefreshService: detailed context analysis | user=%s", user_id)
|
||||
if base_context:
|
||||
website_analysis = base_context.get('website_analysis', {})
|
||||
research_preferences = base_context.get('research_preferences', {})
|
||||
api_keys_data = base_context.get('api_keys_data', {})
|
||||
onboarding_session = base_context.get('onboarding_session', {})
|
||||
|
||||
logger.info(f" - Website analysis keys: {list(website_analysis.keys()) if website_analysis else 'None'}")
|
||||
logger.info(f" - Research preferences keys: {list(research_preferences.keys()) if research_preferences else 'None'}")
|
||||
logger.info(f" - API keys data keys: {list(api_keys_data.keys()) if api_keys_data else 'None'}")
|
||||
logger.info(f" - Onboarding session keys: {list(onboarding_session.keys()) if onboarding_session else 'None'}")
|
||||
|
||||
# Log specific data points
|
||||
if website_analysis:
|
||||
logger.info(f" - Website URL: {website_analysis.get('website_url', 'Not found')}")
|
||||
logger.info(f" - Website status: {website_analysis.get('status', 'Unknown')}")
|
||||
if research_preferences:
|
||||
logger.info(f" - Research depth: {research_preferences.get('research_depth', 'Not found')}")
|
||||
logger.info(f" - Content types: {research_preferences.get('content_types', 'Not found')}")
|
||||
if api_keys_data:
|
||||
logger.info(f" - API providers: {api_keys_data.get('providers', [])}")
|
||||
logger.info(f" - Total keys: {api_keys_data.get('total_keys', 0)}")
|
||||
else:
|
||||
logger.warning(f"AutoFillRefreshService: no base context available | user=%s", user_id)
|
||||
|
||||
try:
|
||||
w = (base_context or {}).get('website_analysis') or {}
|
||||
r = (base_context or {}).get('research_preferences') or {}
|
||||
logger.debug("AutoFillRefreshService: website keys=%s | research keys=%s", len(list(w.keys())) if hasattr(w,'keys') else 0, len(list(r.keys())) if hasattr(r,'keys') else 0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 🚨 CRITICAL: Always use AI-only generation for refresh to ensure real AI values
|
||||
if use_ai:
|
||||
logger.info("AutoFillRefreshService: FORCING AI-only generation for refresh to ensure real AI values")
|
||||
try:
|
||||
ai_payload = await self.structured_ai.generate_autofill_fields(user_id, base_context)
|
||||
meta = ai_payload.get('meta') or {}
|
||||
logger.info("AI-only payload meta: ai_used=%s overrides=%s", meta.get('ai_used'), meta.get('ai_overrides_count'))
|
||||
|
||||
# Log detailed AI payload analysis
|
||||
logger.info(f"AutoFillRefreshService: AI payload analysis | user=%s", user_id)
|
||||
logger.info(f" - AI used: {meta.get('ai_used', False)}")
|
||||
logger.info(f" - AI overrides count: {meta.get('ai_overrides_count', 0)}")
|
||||
logger.info(f" - Success rate: {meta.get('success_rate', 0):.1f}%")
|
||||
logger.info(f" - Attempts: {meta.get('attempts', 0)}")
|
||||
logger.info(f" - Missing fields: {len(meta.get('missing_fields', []))}")
|
||||
logger.info(f" - Fields generated: {len(ai_payload.get('fields', {}))}")
|
||||
|
||||
# 🚨 VALIDATION: Ensure we have real AI-generated data
|
||||
if not meta.get('ai_used', False) or meta.get('ai_overrides_count', 0) == 0:
|
||||
logger.error("❌ CRITICAL: AI generation failed to produce real values - returning error")
|
||||
return {
|
||||
'fields': {},
|
||||
'sources': {},
|
||||
'meta': {
|
||||
'ai_used': False,
|
||||
'ai_overrides_count': 0,
|
||||
'ai_override_fields': [],
|
||||
'ai_only': True,
|
||||
'error': 'AI generation failed to produce real values. Please try again.',
|
||||
'data_source': 'ai_generation_failed'
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("✅ SUCCESS: Real AI-generated values produced")
|
||||
return ai_payload
|
||||
except Exception as e:
|
||||
logger.error("AI-only structured generation failed | user=%s | err=%s", user_id, repr(e))
|
||||
logger.error("Traceback:\n%s", traceback.format_exc())
|
||||
# Return error instead of fallback to prevent stale data
|
||||
return {
|
||||
'fields': {},
|
||||
'sources': {},
|
||||
'meta': {
|
||||
'ai_used': False,
|
||||
'ai_overrides_count': 0,
|
||||
'ai_override_fields': [],
|
||||
'ai_only': True,
|
||||
'error': f'AI generation failed: {str(e)}. Please try again.',
|
||||
'data_source': 'ai_generation_error'
|
||||
}
|
||||
}
|
||||
|
||||
# 🚨 CRITICAL: If AI is disabled, return error instead of stale database data
|
||||
logger.error("❌ CRITICAL: AI generation is disabled - cannot provide real AI values")
|
||||
return {
|
||||
'fields': {},
|
||||
'sources': {},
|
||||
'meta': {
|
||||
'ai_used': False,
|
||||
'ai_overrides_count': 0,
|
||||
'ai_override_fields': [],
|
||||
'ai_only': False,
|
||||
'error': 'AI generation is required for refresh. Please enable AI and try again.',
|
||||
'data_source': 'ai_disabled'
|
||||
}
|
||||
}
|
||||
|
||||
async def build_fresh_payload_with_transparency(self, user_id: int, use_ai: bool = True, ai_only: bool = False, yield_callback=None) -> Dict[str, Any]:
|
||||
"""Build a fresh auto-fill payload with transparency messages.
|
||||
|
||||
Args:
|
||||
user_id: User ID to build payload for
|
||||
use_ai: Whether to use AI augmentation
|
||||
ai_only: Whether to use AI-only generation
|
||||
yield_callback: Callback function to yield transparency messages
|
||||
"""
|
||||
logger.info(f"AutoFillRefreshService: starting build_fresh_payload_with_transparency | user=%s | use_ai=%s | ai_only=%s", user_id, use_ai, ai_only)
|
||||
|
||||
# Phase 1: Initialization
|
||||
if yield_callback:
|
||||
logger.info("AutoFillRefreshService: generating autofill_initialization message")
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_initialization'))
|
||||
|
||||
# Phase 2: Data Collection
|
||||
if yield_callback:
|
||||
logger.info("AutoFillRefreshService: generating autofill_data_collection message")
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_data_collection'))
|
||||
|
||||
# Base context from onboarding analysis
|
||||
logger.debug("AutoFillRefreshService: processing onboarding context | user=%s", user_id)
|
||||
base_context = await self.autofill.integration.process_onboarding_data(user_id, self.db)
|
||||
|
||||
# Phase 3: Data Quality Assessment
|
||||
if yield_callback:
|
||||
data_source_summary = self.transparency.get_data_source_summary(base_context)
|
||||
context = {'data_sources': data_source_summary}
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_data_quality', context))
|
||||
|
||||
# Phase 4: Context Analysis
|
||||
if yield_callback:
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_context_analysis'))
|
||||
|
||||
# Phase 5: Strategy Generation
|
||||
if yield_callback:
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_strategy_generation'))
|
||||
|
||||
if ai_only and use_ai:
|
||||
logger.info("AutoFillRefreshService: AI-only refresh enabled; generating full 30+ fields via AI")
|
||||
|
||||
# Phase 6: Field Generation
|
||||
if yield_callback:
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_field_generation'))
|
||||
|
||||
try:
|
||||
ai_payload = await self.structured_ai.generate_autofill_fields(user_id, base_context)
|
||||
meta = ai_payload.get('meta') or {}
|
||||
|
||||
# 🚨 VALIDATION: Ensure we have real AI-generated data
|
||||
if not meta.get('ai_used', False) or meta.get('ai_overrides_count', 0) == 0:
|
||||
logger.error("❌ CRITICAL: AI generation failed to produce real values - returning error")
|
||||
return {
|
||||
'fields': {},
|
||||
'sources': {},
|
||||
'meta': {
|
||||
'ai_used': False,
|
||||
'ai_overrides_count': 0,
|
||||
'ai_override_fields': [],
|
||||
'ai_only': True,
|
||||
'error': 'AI generation failed to produce real values. Please try again.',
|
||||
'data_source': 'ai_generation_failed'
|
||||
}
|
||||
}
|
||||
|
||||
# Phase 7: Quality Validation
|
||||
if yield_callback:
|
||||
validation_context = {
|
||||
'validation_results': {
|
||||
'passed': len(ai_payload.get('fields', {})),
|
||||
'total': 30 # Approximate total fields
|
||||
}
|
||||
}
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_quality_validation', validation_context))
|
||||
|
||||
# Phase 8: Alignment Check
|
||||
if yield_callback:
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_alignment_check'))
|
||||
|
||||
# Phase 9: Final Review
|
||||
if yield_callback:
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_final_review'))
|
||||
|
||||
# Phase 10: Complete
|
||||
if yield_callback:
|
||||
logger.info("AutoFillRefreshService: generating autofill_complete message")
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_complete'))
|
||||
|
||||
logger.info("✅ SUCCESS: Real AI-generated values produced with transparency")
|
||||
return ai_payload
|
||||
except Exception as e:
|
||||
logger.error("AI-only structured generation failed | user=%s | err=%s", user_id, repr(e))
|
||||
logger.error("Traceback:\n%s", traceback.format_exc())
|
||||
return {
|
||||
'fields': {},
|
||||
'sources': {},
|
||||
'meta': {
|
||||
'ai_used': False,
|
||||
'ai_overrides_count': 0,
|
||||
'ai_override_fields': [],
|
||||
'ai_only': True,
|
||||
'error': f'AI generation failed: {str(e)}. Please try again.',
|
||||
'data_source': 'ai_generation_error'
|
||||
}
|
||||
}
|
||||
|
||||
# 🚨 CRITICAL: Force AI generation for refresh - no fallback to database
|
||||
if use_ai:
|
||||
logger.info("AutoFillRefreshService: FORCING AI generation for refresh to ensure real AI values")
|
||||
|
||||
# Phase 6: Field Generation (for AI generation)
|
||||
if yield_callback:
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_field_generation'))
|
||||
|
||||
try:
|
||||
ai_payload = await self.structured_ai.generate_autofill_fields(user_id, base_context)
|
||||
meta = ai_payload.get('meta') or {}
|
||||
|
||||
# 🚨 VALIDATION: Ensure we have real AI-generated data
|
||||
if not meta.get('ai_used', False) or meta.get('ai_overrides_count', 0) == 0:
|
||||
logger.error("❌ CRITICAL: AI generation failed to produce real values - returning error")
|
||||
return {
|
||||
'fields': {},
|
||||
'sources': {},
|
||||
'meta': {
|
||||
'ai_used': False,
|
||||
'ai_overrides_count': 0,
|
||||
'ai_override_fields': [],
|
||||
'ai_only': False,
|
||||
'error': 'AI generation failed to produce real values. Please try again.',
|
||||
'data_source': 'ai_generation_failed'
|
||||
}
|
||||
}
|
||||
|
||||
# Phase 7-10: Validation, Alignment, Review, Complete
|
||||
if yield_callback:
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_quality_validation'))
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_alignment_check'))
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_final_review'))
|
||||
await yield_callback(self.transparency.generate_phase_message('autofill_complete'))
|
||||
|
||||
logger.info("✅ SUCCESS: Real AI-generated values produced with transparency")
|
||||
return ai_payload
|
||||
except Exception as e:
|
||||
logger.error("AI generation failed | user=%s | err=%s", user_id, repr(e))
|
||||
logger.error("Traceback:\n%s", traceback.format_exc())
|
||||
return {
|
||||
'fields': {},
|
||||
'sources': {},
|
||||
'meta': {
|
||||
'ai_used': False,
|
||||
'ai_overrides_count': 0,
|
||||
'ai_override_fields': [],
|
||||
'ai_only': False,
|
||||
'error': f'AI generation failed: {str(e)}. Please try again.',
|
||||
'data_source': 'ai_generation_error'
|
||||
}
|
||||
}
|
||||
|
||||
# 🚨 CRITICAL: If AI is disabled, return error instead of stale database data
|
||||
logger.error("❌ CRITICAL: AI generation is disabled - cannot provide real AI values")
|
||||
return {
|
||||
'fields': {},
|
||||
'sources': {},
|
||||
'meta': {
|
||||
'ai_used': False,
|
||||
'ai_overrides_count': 0,
|
||||
'ai_override_fields': [],
|
||||
'ai_only': False,
|
||||
'error': 'AI generation is required for refresh. Please enable AI and try again.',
|
||||
'data_source': 'ai_disabled'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,768 @@
|
||||
import json
|
||||
import logging
|
||||
import traceback
|
||||
from typing import Any, Dict, List
|
||||
from datetime import datetime
|
||||
|
||||
from services.ai_service_manager import AIServiceManager, AIServiceType
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Complete core fields - all 30+ fields that the frontend expects
|
||||
CORE_FIELDS = [
|
||||
# Business Context (8 fields)
|
||||
'business_objectives', 'target_metrics', 'content_budget', 'team_size', 'implementation_timeline',
|
||||
'market_share', 'competitive_position', 'performance_metrics',
|
||||
|
||||
# Audience Intelligence (6 fields)
|
||||
'content_preferences', 'consumption_patterns', 'audience_pain_points',
|
||||
'buying_journey', 'seasonal_trends', 'engagement_metrics',
|
||||
|
||||
# Competitive Intelligence (5 fields)
|
||||
'top_competitors', 'competitor_content_strategies', 'market_gaps', 'industry_trends', 'emerging_trends',
|
||||
|
||||
# Content Strategy (7 fields)
|
||||
'preferred_formats', 'content_mix', 'content_frequency', 'optimal_timing',
|
||||
'quality_metrics', 'editorial_guidelines', 'brand_voice',
|
||||
|
||||
# Performance & Analytics (4 fields)
|
||||
'traffic_sources', 'conversion_rates', 'content_roi_targets', 'ab_testing_capabilities'
|
||||
]
|
||||
|
||||
JSON_FIELDS = {
|
||||
'business_objectives', 'target_metrics', 'content_preferences', 'consumption_patterns',
|
||||
'audience_pain_points', 'buying_journey', 'seasonal_trends', 'engagement_metrics',
|
||||
'competitor_content_strategies', 'market_gaps', 'industry_trends', 'emerging_trends',
|
||||
'content_mix', 'optimal_timing', 'quality_metrics', 'editorial_guidelines',
|
||||
'conversion_rates', 'content_roi_targets', 'performance_metrics'
|
||||
}
|
||||
|
||||
ARRAY_FIELDS = {
|
||||
'preferred_formats', 'top_competitors', 'market_gaps', 'industry_trends', 'traffic_sources'
|
||||
}
|
||||
|
||||
# Select field options mapping for value normalization
|
||||
SELECT_FIELD_OPTIONS = {
|
||||
'implementation_timeline': ['3 months', '6 months', '1 year', '2 years', 'Ongoing'],
|
||||
'competitive_position': ['Leader', 'Challenger', 'Niche', 'Emerging'],
|
||||
'content_frequency': ['Daily', 'Weekly', 'Bi-weekly', 'Monthly', 'Quarterly'],
|
||||
'brand_voice': ['Professional', 'Casual', 'Friendly', 'Authoritative', 'Innovative']
|
||||
}
|
||||
|
||||
class AIStructuredAutofillService:
|
||||
"""Generate the complete Strategy Builder fields strictly from AI using onboarding context only."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.ai = AIServiceManager()
|
||||
self.max_retries = 2 # Maximum retry attempts for malformed JSON
|
||||
|
||||
def _build_context_summary(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
||||
website = context.get('website_analysis') or {}
|
||||
research = context.get('research_preferences') or {}
|
||||
api_keys = context.get('api_keys_data') or {}
|
||||
session = context.get('onboarding_session') or {}
|
||||
|
||||
# Extract detailed personalization data
|
||||
writing_style = website.get('writing_style', {})
|
||||
target_audience = website.get('target_audience', {})
|
||||
content_type = website.get('content_type', {})
|
||||
recommended_settings = website.get('recommended_settings', {})
|
||||
content_characteristics = website.get('content_characteristics', {})
|
||||
|
||||
summary = {
|
||||
'user_profile': {
|
||||
'website_url': website.get('website_url'),
|
||||
'business_size': session.get('business_size'),
|
||||
'region': session.get('region'),
|
||||
'onboarding_progress': session.get('progress', 0)
|
||||
},
|
||||
'content_analysis': {
|
||||
'writing_style': {
|
||||
'tone': writing_style.get('tone'),
|
||||
'voice': writing_style.get('voice'),
|
||||
'complexity': writing_style.get('complexity'),
|
||||
'engagement_level': writing_style.get('engagement_level')
|
||||
},
|
||||
'content_characteristics': {
|
||||
'sentence_structure': content_characteristics.get('sentence_structure'),
|
||||
'vocabulary': content_characteristics.get('vocabulary'),
|
||||
'paragraph_organization': content_characteristics.get('paragraph_organization')
|
||||
},
|
||||
'content_type': {
|
||||
'primary_type': content_type.get('primary_type'),
|
||||
'secondary_types': content_type.get('secondary_types'),
|
||||
'purpose': content_type.get('purpose')
|
||||
}
|
||||
},
|
||||
'audience_insights': {
|
||||
'demographics': target_audience.get('demographics'),
|
||||
'expertise_level': target_audience.get('expertise_level'),
|
||||
'industry_focus': target_audience.get('industry_focus'),
|
||||
'pain_points': target_audience.get('pain_points'),
|
||||
'content_preferences': target_audience.get('content_preferences')
|
||||
},
|
||||
'ai_recommendations': {
|
||||
'recommended_tone': recommended_settings.get('writing_tone'),
|
||||
'recommended_audience': recommended_settings.get('target_audience'),
|
||||
'recommended_content_type': recommended_settings.get('content_type'),
|
||||
'style_guidelines': website.get('style_guidelines')
|
||||
},
|
||||
'research_config': {
|
||||
'research_depth': research.get('research_depth'),
|
||||
'content_types': research.get('content_types'),
|
||||
'auto_research': research.get('auto_research'),
|
||||
'factual_content': research.get('factual_content')
|
||||
},
|
||||
'api_capabilities': {
|
||||
'providers': api_keys.get('providers', []),
|
||||
'total_keys': api_keys.get('total_keys', 0),
|
||||
'available_services': self._extract_available_services(api_keys)
|
||||
},
|
||||
'data_quality': {
|
||||
'website_freshness': website.get('data_freshness'),
|
||||
'confidence_level': website.get('confidence_level'),
|
||||
'analysis_status': website.get('status')
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
logger.debug(
|
||||
"AI Structured Autofill: personalized context | website=%s research=%s api=%s session=%s",
|
||||
bool(website), bool(research), bool(api_keys), bool(session)
|
||||
)
|
||||
logger.debug(
|
||||
"AI Structured Autofill: personalization data | writing_style=%s target_audience=%s content_type=%s",
|
||||
bool(writing_style), bool(target_audience), bool(content_type)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return summary
|
||||
|
||||
def _extract_available_services(self, api_keys: Dict[str, Any]) -> List[str]:
|
||||
"""Extract available services from API keys."""
|
||||
services = []
|
||||
providers = api_keys.get('providers', [])
|
||||
|
||||
# Map providers to services
|
||||
provider_service_map = {
|
||||
'google_search_console': ['SEO Analytics', 'Search Performance'],
|
||||
'google_analytics': ['Web Analytics', 'User Behavior'],
|
||||
'semrush': ['Competitive Analysis', 'Keyword Research'],
|
||||
'ahrefs': ['Backlink Analysis', 'SEO Tools'],
|
||||
'moz': ['SEO Tools', 'Rank Tracking'],
|
||||
'social_media': ['Social Media Analytics', 'Social Listening']
|
||||
}
|
||||
|
||||
for provider in providers:
|
||||
if provider in provider_service_map:
|
||||
services.extend(provider_service_map[provider])
|
||||
|
||||
return list(set(services)) # Remove duplicates
|
||||
|
||||
def _build_schema(self) -> Dict[str, Any]:
|
||||
# Simplified schema following Gemini best practices
|
||||
# Reduce complexity by flattening nested structures and simplifying constraints
|
||||
properties: Dict[str, Any] = {}
|
||||
|
||||
# Simplified field definitions - avoid complex constraints that cause 400 errors
|
||||
field_definitions = {
|
||||
# Core business fields (simplified)
|
||||
'business_objectives': {"type": "STRING", "description": "Business goals and objectives"},
|
||||
'target_metrics': {"type": "STRING", "description": "KPIs and success metrics"},
|
||||
'content_budget': {"type": "NUMBER", "description": "Monthly content budget in dollars"},
|
||||
'team_size': {"type": "NUMBER", "description": "Number of people in content team"},
|
||||
'implementation_timeline': {"type": "STRING", "description": "Strategy implementation timeline"},
|
||||
'market_share': {"type": "STRING", "description": "Current market share percentage"},
|
||||
'competitive_position': {"type": "STRING", "description": "Market competitive position"},
|
||||
'performance_metrics': {"type": "STRING", "description": "Current performance data"},
|
||||
|
||||
# Audience fields (simplified)
|
||||
'content_preferences': {"type": "STRING", "description": "Content format and topic preferences"},
|
||||
'consumption_patterns': {"type": "STRING", "description": "When and how audience consumes content"},
|
||||
'audience_pain_points': {"type": "STRING", "description": "Key audience challenges and pain points"},
|
||||
'buying_journey': {"type": "STRING", "description": "Customer journey stages and touchpoints"},
|
||||
'seasonal_trends': {"type": "STRING", "description": "Seasonal content patterns and trends"},
|
||||
'engagement_metrics': {"type": "STRING", "description": "Current engagement data and metrics"},
|
||||
|
||||
# Competitive fields (simplified)
|
||||
'top_competitors': {"type": "STRING", "description": "Main competitors"},
|
||||
'competitor_content_strategies': {"type": "STRING", "description": "Analysis of competitor content approaches"},
|
||||
'market_gaps': {"type": "STRING", "description": "Market opportunities and gaps"},
|
||||
'industry_trends': {"type": "STRING", "description": "Current industry trends"},
|
||||
'emerging_trends': {"type": "STRING", "description": "Upcoming trends and opportunities"},
|
||||
|
||||
# Content strategy fields (simplified)
|
||||
'preferred_formats': {"type": "STRING", "description": "Preferred content formats"},
|
||||
'content_mix': {"type": "STRING", "description": "Content mix distribution"},
|
||||
'content_frequency': {"type": "STRING", "description": "Content publishing frequency"},
|
||||
'optimal_timing': {"type": "STRING", "description": "Best times for publishing content"},
|
||||
'quality_metrics': {"type": "STRING", "description": "Content quality standards and metrics"},
|
||||
'editorial_guidelines': {"type": "STRING", "description": "Style and tone guidelines"},
|
||||
'brand_voice': {"type": "STRING", "description": "Brand voice and tone"},
|
||||
|
||||
# Performance fields (simplified)
|
||||
'traffic_sources': {"type": "STRING", "description": "Primary traffic sources"},
|
||||
'conversion_rates': {"type": "STRING", "description": "Target conversion rates and metrics"},
|
||||
'content_roi_targets': {"type": "STRING", "description": "ROI goals and targets for content"},
|
||||
'ab_testing_capabilities': {"type": "BOOLEAN", "description": "Whether A/B testing capabilities are available"}
|
||||
}
|
||||
|
||||
# Build properties from field definitions
|
||||
for field_id in CORE_FIELDS:
|
||||
if field_id in field_definitions:
|
||||
properties[field_id] = field_definitions[field_id]
|
||||
else:
|
||||
# Fallback for any missing fields
|
||||
properties[field_id] = {"type": "STRING", "description": f"Value for {field_id}"}
|
||||
|
||||
# Use propertyOrdering as recommended by Gemini docs for consistent output
|
||||
schema = {
|
||||
"type": "OBJECT",
|
||||
"properties": properties,
|
||||
"required": CORE_FIELDS, # Make all fields required
|
||||
"propertyOrdering": CORE_FIELDS, # Critical for consistent JSON output
|
||||
"description": "Content strategy fields with simplified constraints"
|
||||
}
|
||||
|
||||
logger.debug("AI Structured Autofill: simplified schema built with %d properties and property ordering", len(CORE_FIELDS))
|
||||
return schema
|
||||
|
||||
def _build_prompt(self, context_summary: Dict[str, Any]) -> str:
|
||||
# Build personalized prompt using actual user data
|
||||
user_profile = context_summary.get('user_profile', {})
|
||||
content_analysis = context_summary.get('content_analysis', {})
|
||||
audience_insights = context_summary.get('audience_insights', {})
|
||||
ai_recommendations = context_summary.get('ai_recommendations', {})
|
||||
research_config = context_summary.get('research_config', {})
|
||||
api_capabilities = context_summary.get('api_capabilities', {})
|
||||
|
||||
# Extract specific personalization data
|
||||
website_url = user_profile.get('website_url', 'your website')
|
||||
writing_tone = content_analysis.get('writing_style', {}).get('tone', 'professional')
|
||||
target_demographics = audience_insights.get('demographics', ['professionals'])
|
||||
industry_focus = audience_insights.get('industry_focus', 'general')
|
||||
expertise_level = audience_insights.get('expertise_level', 'intermediate')
|
||||
primary_content_type = content_analysis.get('content_type', {}).get('primary_type', 'blog')
|
||||
research_depth = research_config.get('research_depth', 'Standard')
|
||||
available_services = api_capabilities.get('available_services', [])
|
||||
|
||||
# Build personalized context description
|
||||
personalization_context = f"""
|
||||
PERSONALIZED CONTEXT FOR {website_url.upper()}:
|
||||
|
||||
🎯 YOUR BUSINESS PROFILE:
|
||||
- Website: {website_url}
|
||||
- Industry Focus: {industry_focus}
|
||||
- Business Size: {user_profile.get('business_size', 'SME')}
|
||||
- Region: {user_profile.get('region', 'Global')}
|
||||
|
||||
📝 YOUR CONTENT ANALYSIS:
|
||||
- Current Writing Tone: {writing_tone}
|
||||
- Primary Content Type: {primary_content_type}
|
||||
- Target Demographics: {', '.join(target_demographics) if isinstance(target_demographics, list) else target_demographics}
|
||||
- Audience Expertise Level: {expertise_level}
|
||||
- Content Purpose: {content_analysis.get('content_type', {}).get('purpose', 'informational')}
|
||||
|
||||
🔍 YOUR AUDIENCE INSIGHTS:
|
||||
- Pain Points: {audience_insights.get('pain_points', 'time constraints, complexity')}
|
||||
- Content Preferences: {audience_insights.get('content_preferences', 'educational, actionable')}
|
||||
- Industry Focus: {industry_focus}
|
||||
|
||||
🤖 AI RECOMMENDATIONS FOR YOUR SITE:
|
||||
- Recommended Tone: {ai_recommendations.get('recommended_tone', writing_tone)}
|
||||
- Recommended Content Type: {ai_recommendations.get('recommended_content_type', primary_content_type)}
|
||||
- Style Guidelines: {ai_recommendations.get('style_guidelines', 'professional, engaging')}
|
||||
|
||||
⚙️ YOUR RESEARCH CONFIGURATION:
|
||||
- Research Depth: {research_depth}
|
||||
- Content Types: {', '.join(research_config.get('content_types', ['blog', 'article'])) if isinstance(research_config.get('content_types'), list) else research_config.get('content_types', 'blog, article')}
|
||||
- Auto Research: {research_config.get('auto_research', True)}
|
||||
- Factual Content: {research_config.get('factual_content', True)}
|
||||
|
||||
🔧 YOUR AVAILABLE TOOLS:
|
||||
- Analytics Services: {', '.join(available_services) if available_services else 'Basic analytics'}
|
||||
- API Providers: {', '.join(api_capabilities.get('providers', [])) if api_capabilities.get('providers') else 'Manual tracking'}
|
||||
"""
|
||||
|
||||
# Personalized prompt with specific instructions
|
||||
prompt = f"""
|
||||
You are a content strategy expert analyzing {website_url}. Based on the detailed analysis of this website and user's onboarding data, generate a personalized content strategy with exactly 30 fields.
|
||||
|
||||
{personalization_context}
|
||||
|
||||
IMPORTANT: Make each field specific to {website_url} and the user's actual data. Avoid generic placeholder values. Use the real insights from their website analysis.
|
||||
|
||||
Generate a JSON object with exactly 30 fields using this exact format:
|
||||
|
||||
{{
|
||||
"business_objectives": "Specific goals for {website_url} based on {industry_focus} industry",
|
||||
"target_metrics": "Realistic KPIs for {user_profile.get('business_size', 'SME')} business",
|
||||
"content_budget": 3000,
|
||||
"team_size": 3,
|
||||
"implementation_timeline": "6 months",
|
||||
"market_share": "15%",
|
||||
"competitive_position": "Leader",
|
||||
"performance_metrics": "Current performance data for {website_url}",
|
||||
"content_preferences": "Content formats preferred by {', '.join(target_demographics) if isinstance(target_demographics, list) else target_demographics} audience",
|
||||
"consumption_patterns": "When {expertise_level} level audience consumes content",
|
||||
"audience_pain_points": "Specific challenges for {industry_focus} professionals",
|
||||
"buying_journey": "Customer journey for {industry_focus} industry",
|
||||
"seasonal_trends": "Seasonal patterns in {industry_focus}",
|
||||
"engagement_metrics": "Expected engagement for {writing_tone} tone content",
|
||||
"top_competitors": "Main competitors in {industry_focus} space",
|
||||
"competitor_content_strategies": "How competitors approach {primary_content_type} content",
|
||||
"market_gaps": "Opportunities in {industry_focus} content market",
|
||||
"industry_trends": "Current trends in {industry_focus} industry",
|
||||
"emerging_trends": "Upcoming trends for {industry_focus}",
|
||||
"preferred_formats": "Formats that work for {expertise_level} audience",
|
||||
"content_mix": "Optimal mix for {primary_content_type} focus",
|
||||
"content_frequency": "Frequency for {research_depth} research depth",
|
||||
"optimal_timing": "Best times for {target_demographics[0] if isinstance(target_demographics, list) and target_demographics else 'your'} audience",
|
||||
"quality_metrics": "Quality standards for {writing_tone} content",
|
||||
"editorial_guidelines": "Guidelines matching {writing_tone} tone",
|
||||
"brand_voice": "{writing_tone.title()}",
|
||||
"traffic_sources": "Primary sources for {industry_focus} content",
|
||||
"conversion_rates": "Realistic rates for {user_profile.get('business_size', 'SME')}",
|
||||
"content_roi_targets": "ROI goals for {industry_focus} content",
|
||||
"ab_testing_capabilities": true
|
||||
}}
|
||||
|
||||
Generate the complete JSON with all 30 fields personalized for {website_url}:
|
||||
"""
|
||||
|
||||
logger.debug("AI Structured Autofill: personalized prompt (%d chars)", len(prompt))
|
||||
return prompt
|
||||
|
||||
def _normalize_value(self, key: str, value: Any) -> Any:
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# Handle numeric fields that might come as text
|
||||
if key in ['content_budget', 'team_size']:
|
||||
if isinstance(value, (int, float)):
|
||||
return value
|
||||
elif isinstance(value, str):
|
||||
# Extract numeric value from text
|
||||
import re
|
||||
# Remove currency symbols, commas, and common words
|
||||
cleaned = re.sub(r'[$,€£¥]', '', value.lower())
|
||||
cleaned = re.sub(r'\b(monthly|yearly|annual|people|person|specialist|creator|writer|editor|team|member)\b', '', cleaned)
|
||||
cleaned = re.sub(r'\s+', ' ', cleaned).strip()
|
||||
|
||||
# Extract first number found
|
||||
numbers = re.findall(r'\d+(?:\.\d+)?', cleaned)
|
||||
if numbers:
|
||||
try:
|
||||
num_value = float(numbers[0])
|
||||
# For team_size, convert to integer
|
||||
if key == 'team_size':
|
||||
return int(num_value)
|
||||
return num_value
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
logger.warning(f"Could not extract numeric value from '{key}' field: '{value}'")
|
||||
return None
|
||||
|
||||
# Handle boolean fields
|
||||
if key == 'ab_testing_capabilities':
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
elif isinstance(value, str):
|
||||
normalized_value = value.lower().strip()
|
||||
if normalized_value in ['true', 'yes', 'available', 'enabled', '1']:
|
||||
return True
|
||||
elif normalized_value in ['false', 'no', 'unavailable', 'disabled', '0']:
|
||||
return False
|
||||
logger.warning(f"Could not parse boolean value for '{key}': '{value}'")
|
||||
return None
|
||||
|
||||
# Handle select fields with predefined options
|
||||
if key in SELECT_FIELD_OPTIONS:
|
||||
if isinstance(value, str):
|
||||
# Try exact match first (case-insensitive)
|
||||
normalized_value = value.lower().strip()
|
||||
for option in SELECT_FIELD_OPTIONS[key]:
|
||||
if normalized_value == option.lower():
|
||||
return option
|
||||
|
||||
# Try partial matching for common variations
|
||||
for option in SELECT_FIELD_OPTIONS[key]:
|
||||
option_lower = option.lower()
|
||||
# Handle common variations
|
||||
if (normalized_value.startswith(option_lower) or
|
||||
option_lower in normalized_value or
|
||||
normalized_value.endswith(option_lower)):
|
||||
return option
|
||||
|
||||
# Special handling for content_frequency
|
||||
if key == 'content_frequency':
|
||||
if 'daily' in normalized_value:
|
||||
return 'Daily'
|
||||
elif 'weekly' in normalized_value or 'week' in normalized_value:
|
||||
return 'Weekly'
|
||||
elif 'bi-weekly' in normalized_value or 'biweekly' in normalized_value:
|
||||
return 'Bi-weekly'
|
||||
elif 'monthly' in normalized_value or 'month' in normalized_value:
|
||||
return 'Monthly'
|
||||
elif 'quarterly' in normalized_value or 'quarter' in normalized_value:
|
||||
return 'Quarterly'
|
||||
|
||||
# If no match found, return the first option as fallback
|
||||
logger.warning(f"Could not normalize select field '{key}' value: '{value}' to valid options: {SELECT_FIELD_OPTIONS[key]}")
|
||||
return SELECT_FIELD_OPTIONS[key][0] # Return first option as fallback
|
||||
|
||||
# For all other fields, ensure they're strings and not empty
|
||||
if isinstance(value, str):
|
||||
# Special handling for multiselect fields
|
||||
if key in ['preferred_formats', 'top_competitors', 'market_gaps', 'industry_trends', 'traffic_sources']:
|
||||
# Split by comma and clean up each item
|
||||
items = [item.strip() for item in value.split(',') if item.strip()]
|
||||
if items:
|
||||
return items # Return as array for multiselect fields
|
||||
return None
|
||||
return value.strip() if value.strip() else None
|
||||
elif isinstance(value, (int, float, bool)):
|
||||
return str(value)
|
||||
elif isinstance(value, list):
|
||||
# For multiselect fields, return the list as-is
|
||||
if key in ['preferred_formats', 'top_competitors', 'market_gaps', 'industry_trends', 'traffic_sources']:
|
||||
return [str(item) for item in value if item]
|
||||
# For other fields, convert arrays to comma-separated strings
|
||||
return ', '.join(str(item) for item in value if item)
|
||||
else:
|
||||
return str(value) if value else None
|
||||
|
||||
def _calculate_success_rate(self, result: Dict[str, Any]) -> float:
|
||||
"""Calculate the percentage of successfully filled fields."""
|
||||
if not isinstance(result, dict):
|
||||
return 0.0
|
||||
|
||||
filled_fields = 0
|
||||
for key in CORE_FIELDS:
|
||||
value = result.get(key)
|
||||
if value is not None and value != "" and value != []:
|
||||
# Additional checks for different data types
|
||||
if isinstance(value, str) and value.strip():
|
||||
filled_fields += 1
|
||||
elif isinstance(value, (int, float)) and value != 0:
|
||||
filled_fields += 1
|
||||
elif isinstance(value, bool):
|
||||
filled_fields += 1
|
||||
elif isinstance(value, list) and len(value) > 0:
|
||||
filled_fields += 1
|
||||
elif value is not None and value != "":
|
||||
filled_fields += 1
|
||||
|
||||
return (filled_fields / len(CORE_FIELDS)) * 100
|
||||
|
||||
def _should_retry(self, result: Dict[str, Any], attempt: int) -> bool:
|
||||
"""Determine if we should retry based on success rate and attempt count."""
|
||||
if attempt >= self.max_retries:
|
||||
return False
|
||||
|
||||
# Check if result has error
|
||||
if 'error' in result:
|
||||
logger.info(f"Retry attempt {attempt + 1} due to error: {result.get('error')}")
|
||||
return True
|
||||
|
||||
# Check success rate - stop immediately if we have 100% success
|
||||
success_rate = self._calculate_success_rate(result)
|
||||
logger.info(f"Success rate: {success_rate:.1f}% (attempt {attempt + 1})")
|
||||
|
||||
# If we have 100% success, don't retry
|
||||
if success_rate >= 100.0:
|
||||
logger.info(f"Perfect success rate achieved: {success_rate:.1f}% - no retry needed")
|
||||
return False
|
||||
|
||||
# Retry if success rate is below 80% (more aggressive than 50%)
|
||||
if success_rate < 80.0:
|
||||
logger.info(f"Retry attempt {attempt + 1} due to low success rate: {success_rate:.1f}% (need 80%+)")
|
||||
return True
|
||||
|
||||
# Also retry if we're missing more than 6 fields (20% of 30 fields)
|
||||
missing_count = len([k for k in CORE_FIELDS if not result.get(k) or result.get(k) == "" or result.get(k) == []])
|
||||
if missing_count > 6:
|
||||
logger.info(f"Retry attempt {attempt + 1} due to too many missing fields: {missing_count} missing (max 6)")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def generate_autofill_fields(self, user_id: int, context: Dict[str, Any]) -> Dict[str, Any]:
|
||||
context_summary = self._build_context_summary(context)
|
||||
schema = self._build_schema()
|
||||
prompt = self._build_prompt(context_summary)
|
||||
|
||||
logger.info("AIStructuredAutofillService: generating %d fields | user=%s", len(CORE_FIELDS), user_id)
|
||||
logger.debug("AIStructuredAutofillService: properties=%d", len(schema.get('properties', {})))
|
||||
|
||||
# Log context summary for debugging
|
||||
logger.info("AIStructuredAutofillService: context summary | user=%s", user_id)
|
||||
logger.info(" - Website analysis exists: %s", bool(context_summary.get('user_profile', {}).get('website_url')))
|
||||
logger.info(" - Research config: %s", context_summary.get('research_config', {}).get('research_depth', 'None'))
|
||||
logger.info(" - API capabilities: %s", len(context_summary.get('api_capabilities', {}).get('providers', [])))
|
||||
logger.info(" - Content analysis: %s", bool(context_summary.get('content_analysis')))
|
||||
logger.info(" - Audience insights: %s", bool(context_summary.get('audience_insights')))
|
||||
|
||||
# Log prompt length for debugging
|
||||
logger.info("AIStructuredAutofillService: prompt length=%d chars | user=%s", len(prompt), user_id)
|
||||
|
||||
last_result = None
|
||||
for attempt in range(self.max_retries + 1):
|
||||
try:
|
||||
logger.info(f"AI structured call attempt {attempt + 1}/{self.max_retries + 1} | user=%s", user_id)
|
||||
result = await self.ai.execute_structured_json_call(
|
||||
service_type=AIServiceType.STRATEGIC_INTELLIGENCE,
|
||||
prompt=prompt,
|
||||
schema=schema
|
||||
)
|
||||
last_result = result
|
||||
|
||||
# Log AI response details
|
||||
logger.info(f"AI response received | attempt={attempt + 1} | user=%s", user_id)
|
||||
if isinstance(result, dict):
|
||||
logger.info(f" - Response keys: {list(result.keys())}")
|
||||
logger.info(f" - Response type: dict with {len(result)} items")
|
||||
|
||||
# Handle wrapped response from AI service manager
|
||||
if 'data' in result and 'success' in result:
|
||||
# This is a wrapped response from AI service manager
|
||||
if result.get('success'):
|
||||
# Extract the actual AI response from the 'data' field
|
||||
ai_response = result.get('data', {})
|
||||
logger.info(f" - Extracted AI response from wrapped response")
|
||||
logger.info(f" - AI response keys: {list(ai_response.keys()) if isinstance(ai_response, dict) else 'N/A'}")
|
||||
last_result = ai_response
|
||||
else:
|
||||
# AI service failed
|
||||
error_msg = result.get('error', 'Unknown AI service error')
|
||||
logger.error(f" - AI service failed: {error_msg}")
|
||||
last_result = {'error': error_msg}
|
||||
elif 'error' in result:
|
||||
logger.error(f" - AI returned error: {result['error']}")
|
||||
else:
|
||||
logger.warning(f" - Response type: {type(result)}")
|
||||
|
||||
# Check if we should retry
|
||||
if not self._should_retry(last_result, attempt):
|
||||
logger.info(f"Retry not needed | attempt={attempt + 1} | user=%s", user_id)
|
||||
break
|
||||
|
||||
# Add a small delay before retry
|
||||
if attempt < self.max_retries:
|
||||
import asyncio
|
||||
await asyncio.sleep(1)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"AI structured call failed (attempt {attempt + 1}) | user=%s | err=%s", user_id, repr(e))
|
||||
logger.error("Traceback:\n%s", traceback.format_exc())
|
||||
last_result = {
|
||||
'error': str(e)
|
||||
}
|
||||
if attempt < self.max_retries:
|
||||
import asyncio
|
||||
await asyncio.sleep(1)
|
||||
continue
|
||||
break
|
||||
|
||||
# Process the final result
|
||||
if not isinstance(last_result, dict):
|
||||
logger.warning("AI did not return a structured JSON object, got: %s", type(last_result))
|
||||
return {
|
||||
'fields': {},
|
||||
'sources': {},
|
||||
'meta': {
|
||||
'ai_used': False,
|
||||
'ai_overrides_count': 0,
|
||||
'missing_fields': CORE_FIELDS,
|
||||
'error': f"AI returned {type(last_result)} instead of dict",
|
||||
'attempts': self.max_retries + 1
|
||||
}
|
||||
}
|
||||
|
||||
# Check if AI returned an error
|
||||
if 'error' in last_result:
|
||||
logger.warning("AI returned error after all attempts: %s", last_result.get('error'))
|
||||
return {
|
||||
'fields': {},
|
||||
'sources': {},
|
||||
'meta': {
|
||||
'ai_used': False,
|
||||
'ai_overrides_count': 0,
|
||||
'missing_fields': CORE_FIELDS,
|
||||
'error': last_result.get('error', 'Unknown AI error'),
|
||||
'attempts': self.max_retries + 1
|
||||
}
|
||||
}
|
||||
|
||||
# Try to extract fields from malformed JSON if needed
|
||||
if len(last_result) < len(CORE_FIELDS) * 0.5: # If we got less than 50% of fields
|
||||
logger.warning("AI returned incomplete result, attempting to extract from raw response")
|
||||
# Try to extract key-value pairs from the raw response
|
||||
extracted_result = self._extract_fields_from_raw_response(last_result)
|
||||
if extracted_result and len(extracted_result) > len(last_result):
|
||||
logger.info("Successfully extracted additional fields from raw response")
|
||||
last_result = extracted_result
|
||||
|
||||
try:
|
||||
logger.debug("AI structured result keys=%d | sample keys=%s", len(list(last_result.keys())), list(last_result.keys())[:8])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Build UI fields map using only non-null normalized values
|
||||
fields: Dict[str, Any] = {}
|
||||
sources: Dict[str, str] = {}
|
||||
non_null_keys = []
|
||||
missing_fields = []
|
||||
|
||||
for key in CORE_FIELDS:
|
||||
raw_value = last_result.get(key)
|
||||
norm_value = self._normalize_value(key, raw_value)
|
||||
if norm_value is not None and norm_value != "" and norm_value != []:
|
||||
# Add personalization metadata to each field
|
||||
personalized_metadata = self._add_personalization_metadata(key, norm_value, context_summary)
|
||||
fields[key] = {
|
||||
'value': norm_value,
|
||||
'source': 'ai_refresh',
|
||||
'confidence': 0.8,
|
||||
'personalized': True,
|
||||
'personalization_data': personalized_metadata
|
||||
}
|
||||
sources[key] = 'ai_refresh'
|
||||
non_null_keys.append(key)
|
||||
else:
|
||||
missing_fields.append(key)
|
||||
|
||||
# Log detailed field analysis
|
||||
logger.info("AI structured autofill field analysis:")
|
||||
logger.info("✅ Generated fields (%d): %s", len(non_null_keys), non_null_keys)
|
||||
logger.info("❌ Missing fields (%d): %s", len(missing_fields), missing_fields)
|
||||
|
||||
# Categorize missing fields
|
||||
field_categories = {
|
||||
'business_context': ['business_objectives', 'target_metrics', 'content_budget', 'team_size', 'implementation_timeline', 'market_share', 'competitive_position', 'performance_metrics'],
|
||||
'audience_intelligence': ['content_preferences', 'consumption_patterns', 'audience_pain_points', 'buying_journey', 'seasonal_trends', 'engagement_metrics'],
|
||||
'competitive_intelligence': ['top_competitors', 'competitor_content_strategies', 'market_gaps', 'industry_trends', 'emerging_trends'],
|
||||
'content_strategy': ['preferred_formats', 'content_mix', 'content_frequency', 'optimal_timing', 'quality_metrics', 'editorial_guidelines', 'brand_voice'],
|
||||
'performance_analytics': ['traffic_sources', 'conversion_rates', 'content_roi_targets', 'ab_testing_capabilities']
|
||||
}
|
||||
|
||||
# Log category-wise success rates
|
||||
for category, category_fields in field_categories.items():
|
||||
generated_count = len([f for f in category_fields if f in non_null_keys])
|
||||
missing_count = len([f for f in category_fields if f in missing_fields])
|
||||
logger.info(f"📊 {category.upper()}: {generated_count}/{len(category_fields)} fields generated ({missing_count} missing: {[f for f in category_fields if f in missing_fields]})")
|
||||
|
||||
success_rate = self._calculate_success_rate(last_result)
|
||||
logger.info(f"AI structured autofill completed | non_null_fields={len(non_null_keys)} missing={len(missing_fields)} success_rate={success_rate:.1f}% attempts={self.max_retries + 1}")
|
||||
|
||||
return {
|
||||
'fields': fields,
|
||||
'sources': sources,
|
||||
'meta': {
|
||||
'ai_used': True,
|
||||
'ai_overrides_count': len(non_null_keys),
|
||||
'missing_fields': missing_fields,
|
||||
'success_rate': success_rate,
|
||||
'attempts': self.max_retries + 1,
|
||||
'personalization_level': 'high',
|
||||
'data_sources_used': list(set(sources.values())),
|
||||
'website_analyzed': context_summary.get('user_profile', {}).get('website_url'),
|
||||
'generated_at': datetime.utcnow().isoformat()
|
||||
}
|
||||
}
|
||||
|
||||
def _add_personalization_metadata(self, field_key: str, value: Any, context_summary: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Add personalization metadata to explain how the value was personalized."""
|
||||
user_profile = context_summary.get('user_profile', {})
|
||||
content_analysis = context_summary.get('content_analysis', {})
|
||||
audience_insights = context_summary.get('audience_insights', {})
|
||||
ai_recommendations = context_summary.get('ai_recommendations', {})
|
||||
|
||||
website_url = user_profile.get('website_url', 'your website')
|
||||
writing_tone = content_analysis.get('writing_style', {}).get('tone', 'professional')
|
||||
industry_focus = audience_insights.get('industry_focus', 'general')
|
||||
expertise_level = audience_insights.get('expertise_level', 'intermediate')
|
||||
|
||||
# Create personalized explanation for each field
|
||||
personalization_explanations = {
|
||||
'business_objectives': f"Based on {industry_focus} industry analysis and {user_profile.get('business_size', 'SME')} business profile",
|
||||
'target_metrics': f"Realistic KPIs for {user_profile.get('business_size', 'SME')} business in {industry_focus}",
|
||||
'content_budget': f"Budget recommendation based on {user_profile.get('business_size', 'SME')} scale and {industry_focus} content needs",
|
||||
'team_size': f"Team size optimized for {user_profile.get('business_size', 'SME')} business and {content_analysis.get('content_type', {}).get('primary_type', 'blog')} content",
|
||||
'implementation_timeline': f"Timeline based on {user_profile.get('business_size', 'SME')} resources and {industry_focus} complexity",
|
||||
'market_share': f"Market position analysis for {industry_focus} industry",
|
||||
'competitive_position': f"Competitive analysis for {industry_focus} market",
|
||||
'performance_metrics': f"Current performance data from {website_url} analysis",
|
||||
'content_preferences': f"Formats preferred by {', '.join(audience_insights.get('demographics', ['professionals']))} audience",
|
||||
'consumption_patterns': f"Patterns for {expertise_level} level audience in {industry_focus}",
|
||||
'audience_pain_points': f"Specific challenges for {industry_focus} professionals",
|
||||
'buying_journey': f"Customer journey mapped for {industry_focus} industry",
|
||||
'seasonal_trends': f"Seasonal patterns specific to {industry_focus} content",
|
||||
'engagement_metrics': f"Expected engagement for {writing_tone} tone content",
|
||||
'top_competitors': f"Main competitors in {industry_focus} space",
|
||||
'competitor_content_strategies': f"Competitor analysis for {industry_focus} content strategies",
|
||||
'market_gaps': f"Opportunities identified in {industry_focus} content market",
|
||||
'industry_trends': f"Current trends in {industry_focus} industry",
|
||||
'emerging_trends': f"Upcoming trends for {industry_focus} content",
|
||||
'preferred_formats': f"Formats optimized for {expertise_level} audience",
|
||||
'content_mix': f"Optimal mix for {content_analysis.get('content_type', {}).get('primary_type', 'blog')} focus",
|
||||
'content_frequency': f"Frequency based on {context_summary.get('research_config', {}).get('research_depth', 'Standard')} research depth",
|
||||
'optimal_timing': f"Best times for {audience_insights.get('demographics', ['professionals'])[0] if isinstance(audience_insights.get('demographics'), list) and audience_insights.get('demographics') else 'your'} audience",
|
||||
'quality_metrics': f"Quality standards for {writing_tone} content",
|
||||
'editorial_guidelines': f"Guidelines matching {writing_tone} tone from {website_url} analysis",
|
||||
'brand_voice': f"Voice derived from {writing_tone} tone analysis of {website_url}",
|
||||
'traffic_sources': f"Primary sources for {industry_focus} content",
|
||||
'conversion_rates': f"Realistic rates for {user_profile.get('business_size', 'SME')} business",
|
||||
'content_roi_targets': f"ROI goals for {industry_focus} content",
|
||||
'ab_testing_capabilities': f"A/B testing availability based on {user_profile.get('business_size', 'SME')} capabilities"
|
||||
}
|
||||
|
||||
return {
|
||||
'explanation': personalization_explanations.get(field_key, f"Personalized for {website_url}"),
|
||||
'data_sources': {
|
||||
'website_analysis': bool(context_summary.get('content_analysis')),
|
||||
'audience_insights': bool(context_summary.get('audience_insights')),
|
||||
'ai_recommendations': bool(context_summary.get('ai_recommendations')),
|
||||
'research_config': bool(context_summary.get('research_config'))
|
||||
},
|
||||
'personalization_factors': {
|
||||
'website_url': website_url,
|
||||
'industry_focus': industry_focus,
|
||||
'writing_tone': writing_tone,
|
||||
'expertise_level': expertise_level,
|
||||
'business_size': user_profile.get('business_size', 'SME')
|
||||
}
|
||||
}
|
||||
|
||||
def _extract_fields_from_raw_response(self, result: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Extract fields from malformed JSON response using regex patterns."""
|
||||
import re
|
||||
|
||||
# Convert result to string for pattern matching
|
||||
result_str = str(result)
|
||||
|
||||
extracted = {}
|
||||
|
||||
# Pattern to match key-value pairs in JSON-like format
|
||||
patterns = [
|
||||
r'"([^"]+)":\s*"([^"]*)"', # String values
|
||||
r'"([^"]+)":\s*(\d+(?:\.\d+)?)', # Numeric values
|
||||
r'"([^"]+)":\s*(true|false)', # Boolean values
|
||||
r'"([^"]+)":\s*\[([^\]]*)\]', # Array values
|
||||
]
|
||||
|
||||
for pattern in patterns:
|
||||
matches = re.findall(pattern, result_str)
|
||||
for key, value in matches:
|
||||
if key in CORE_FIELDS:
|
||||
# Clean up the value
|
||||
if value.lower() in ['true', 'false']:
|
||||
extracted[key] = value.lower() == 'true'
|
||||
elif value.replace('.', '').isdigit():
|
||||
extracted[key] = float(value) if '.' in value else int(value)
|
||||
else:
|
||||
extracted[key] = value.strip('"')
|
||||
|
||||
logger.info("Extracted %d fields from raw response: %s", len(extracted), list(extracted.keys()))
|
||||
return extracted
|
||||
@@ -0,0 +1,79 @@
|
||||
from typing import Any, Dict, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..onboarding.data_integration import OnboardingDataIntegrationService
|
||||
|
||||
# Local module imports (to be created in this batch)
|
||||
from .normalizers.website_normalizer import normalize_website_analysis
|
||||
from .normalizers.research_normalizer import normalize_research_preferences
|
||||
from .normalizers.api_keys_normalizer import normalize_api_keys
|
||||
from .transformer import transform_to_fields
|
||||
from .quality import calculate_quality_scores_from_raw, calculate_confidence_from_raw, calculate_data_freshness
|
||||
from .transparency import build_data_sources_map, build_input_data_points
|
||||
from .schema import validate_output
|
||||
|
||||
|
||||
class AutoFillService:
|
||||
"""Facade for building Content Strategy auto-fill payload."""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
self.integration = OnboardingDataIntegrationService()
|
||||
|
||||
async def get_autofill(self, user_id: int) -> Dict[str, Any]:
|
||||
# 1) Collect raw integration data
|
||||
integrated = await self.integration.process_onboarding_data(user_id, self.db)
|
||||
if not integrated:
|
||||
raise RuntimeError("No onboarding data available for user")
|
||||
|
||||
website_raw = integrated.get('website_analysis', {})
|
||||
research_raw = integrated.get('research_preferences', {})
|
||||
api_raw = integrated.get('api_keys_data', {})
|
||||
session_raw = integrated.get('onboarding_session', {})
|
||||
|
||||
# 2) Normalize raw sources
|
||||
website = await normalize_website_analysis(website_raw)
|
||||
research = await normalize_research_preferences(research_raw)
|
||||
api_keys = await normalize_api_keys(api_raw)
|
||||
|
||||
# 3) Quality/confidence/freshness (computed from raw, but returned as meta)
|
||||
quality_scores = calculate_quality_scores_from_raw({
|
||||
'website_analysis': website_raw,
|
||||
'research_preferences': research_raw,
|
||||
'api_keys_data': api_raw,
|
||||
})
|
||||
confidence_levels = calculate_confidence_from_raw({
|
||||
'website_analysis': website_raw,
|
||||
'research_preferences': research_raw,
|
||||
'api_keys_data': api_raw,
|
||||
})
|
||||
data_freshness = calculate_data_freshness(session_raw)
|
||||
|
||||
# 4) Transform to frontend field map
|
||||
fields = transform_to_fields(
|
||||
website=website,
|
||||
research=research,
|
||||
api_keys=api_keys,
|
||||
session=session_raw,
|
||||
)
|
||||
|
||||
# 5) Transparency maps
|
||||
sources = build_data_sources_map(website, research, api_keys)
|
||||
input_data_points = build_input_data_points(
|
||||
website_raw=website_raw,
|
||||
research_raw=research_raw,
|
||||
api_raw=api_raw,
|
||||
)
|
||||
|
||||
payload = {
|
||||
'fields': fields,
|
||||
'sources': sources,
|
||||
'quality_scores': quality_scores,
|
||||
'confidence_levels': confidence_levels,
|
||||
'data_freshness': data_freshness,
|
||||
'input_data_points': input_data_points,
|
||||
}
|
||||
|
||||
# Validate structure strictly
|
||||
validate_output(payload)
|
||||
return payload
|
||||
@@ -0,0 +1,25 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
async def normalize_api_keys(api_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if not api_data:
|
||||
return {}
|
||||
|
||||
providers = api_data.get('providers', [])
|
||||
|
||||
return {
|
||||
'analytics_data': {
|
||||
'google_analytics': {
|
||||
'connected': 'google_analytics' in providers,
|
||||
'metrics': api_data.get('google_analytics', {}).get('metrics', {})
|
||||
},
|
||||
'google_search_console': {
|
||||
'connected': 'google_search_console' in providers,
|
||||
'metrics': api_data.get('google_search_console', {}).get('metrics', {})
|
||||
}
|
||||
},
|
||||
'social_media_data': api_data.get('social_media_data', {}),
|
||||
'competitor_data': api_data.get('competitor_data', {}),
|
||||
'data_quality': api_data.get('data_quality'),
|
||||
'confidence_level': api_data.get('confidence_level', 0.8),
|
||||
'data_freshness': api_data.get('data_freshness', 0.8)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
async def normalize_research_preferences(research_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if not research_data:
|
||||
return {}
|
||||
|
||||
return {
|
||||
'content_preferences': {
|
||||
'preferred_formats': research_data.get('content_types', []),
|
||||
'content_topics': research_data.get('research_topics', []),
|
||||
'content_style': research_data.get('writing_style', {}).get('tone', []),
|
||||
'content_length': 'Medium (1000-2000 words)',
|
||||
'visual_preferences': ['Infographics', 'Charts', 'Diagrams'],
|
||||
},
|
||||
'audience_intelligence': {
|
||||
'target_audience': research_data.get('target_audience', {}).get('demographics', []),
|
||||
'pain_points': research_data.get('target_audience', {}).get('pain_points', []),
|
||||
'buying_journey': research_data.get('target_audience', {}).get('buying_journey', {}),
|
||||
'consumption_patterns': research_data.get('target_audience', {}).get('consumption_patterns', {}),
|
||||
},
|
||||
'research_goals': {
|
||||
'primary_goals': research_data.get('research_topics', []),
|
||||
'secondary_goals': research_data.get('content_types', []),
|
||||
'success_metrics': ['Website traffic', 'Lead quality', 'Engagement rates'],
|
||||
},
|
||||
'data_quality': research_data.get('data_quality'),
|
||||
'confidence_level': research_data.get('confidence_level', 0.8),
|
||||
'data_freshness': research_data.get('data_freshness', 0.8),
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
async def normalize_website_analysis(website_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if not website_data:
|
||||
return {}
|
||||
|
||||
processed_data = {
|
||||
'website_url': website_data.get('website_url'),
|
||||
'industry': website_data.get('target_audience', {}).get('industry_focus'),
|
||||
'market_position': 'Emerging',
|
||||
'business_size': 'Medium',
|
||||
'target_audience': website_data.get('target_audience', {}).get('demographics'),
|
||||
'content_goals': website_data.get('content_type', {}).get('purpose', []),
|
||||
'performance_metrics': {
|
||||
'traffic': website_data.get('performance_metrics', {}).get('traffic', 10000),
|
||||
'conversion_rate': website_data.get('performance_metrics', {}).get('conversion_rate', 2.5),
|
||||
'bounce_rate': website_data.get('performance_metrics', {}).get('bounce_rate', 50.0),
|
||||
'avg_session_duration': website_data.get('performance_metrics', {}).get('avg_session_duration', 150),
|
||||
'estimated_market_share': website_data.get('performance_metrics', {}).get('estimated_market_share')
|
||||
},
|
||||
'traffic_sources': website_data.get('traffic_sources', {
|
||||
'organic': 70,
|
||||
'social': 20,
|
||||
'direct': 7,
|
||||
'referral': 3
|
||||
}),
|
||||
'content_gaps': website_data.get('style_guidelines', {}).get('content_gaps', []),
|
||||
'topics': website_data.get('content_type', {}).get('primary_type', []),
|
||||
'content_quality_score': website_data.get('content_quality_score', 7.5),
|
||||
'seo_opportunities': website_data.get('style_guidelines', {}).get('seo_opportunities', []),
|
||||
'competitors': website_data.get('competitors', []),
|
||||
'competitive_advantages': website_data.get('style_guidelines', {}).get('advantages', []),
|
||||
'market_gaps': website_data.get('style_guidelines', {}).get('market_gaps', []),
|
||||
'data_quality': website_data.get('data_quality'),
|
||||
'confidence_level': website_data.get('confidence_level', 0.8),
|
||||
'data_freshness': website_data.get('data_freshness', 0.8),
|
||||
'content_budget': website_data.get('content_budget'),
|
||||
'team_size': website_data.get('team_size'),
|
||||
'implementation_timeline': website_data.get('implementation_timeline'),
|
||||
'market_share': website_data.get('market_share'),
|
||||
'target_metrics': website_data.get('target_metrics'),
|
||||
}
|
||||
|
||||
return processed_data
|
||||
@@ -0,0 +1,61 @@
|
||||
from typing import Any, Dict
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def calculate_quality_scores_from_raw(data_sources: Dict[str, Any]) -> Dict[str, float]:
|
||||
scores: Dict[str, float] = {}
|
||||
for source, data in data_sources.items():
|
||||
if isinstance(data, dict) and data:
|
||||
total = len(data)
|
||||
non_null = len([v for v in data.values() if v is not None])
|
||||
scores[source] = (non_null / total) * 100 if total else 0.0
|
||||
else:
|
||||
scores[source] = 0.0
|
||||
return scores
|
||||
|
||||
|
||||
def calculate_confidence_from_raw(data_sources: Dict[str, Any]) -> Dict[str, float]:
|
||||
levels: Dict[str, float] = {}
|
||||
if data_sources.get('website_analysis'):
|
||||
levels['website_analysis'] = data_sources['website_analysis'].get('confidence_level', 0.8)
|
||||
if data_sources.get('research_preferences'):
|
||||
levels['research_preferences'] = data_sources['research_preferences'].get('confidence_level', 0.7)
|
||||
if data_sources.get('api_keys_data'):
|
||||
levels['api_keys_data'] = data_sources['api_keys_data'].get('confidence_level', 0.6)
|
||||
return levels
|
||||
|
||||
|
||||
def calculate_data_freshness(onboarding_session: Any) -> Dict[str, Any]:
|
||||
try:
|
||||
updated_at = None
|
||||
if hasattr(onboarding_session, 'updated_at'):
|
||||
updated_at = onboarding_session.updated_at
|
||||
elif isinstance(onboarding_session, dict):
|
||||
updated_at = onboarding_session.get('last_updated') or onboarding_session.get('updated_at')
|
||||
|
||||
if not updated_at:
|
||||
return {'status': 'unknown', 'age_days': 'unknown'}
|
||||
|
||||
if isinstance(updated_at, str):
|
||||
try:
|
||||
updated_at = datetime.fromisoformat(updated_at.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
return {'status': 'unknown', 'age_days': 'unknown'}
|
||||
|
||||
age_days = (datetime.utcnow() - updated_at).days
|
||||
if age_days <= 7:
|
||||
status = 'fresh'
|
||||
elif age_days <= 30:
|
||||
status = 'recent'
|
||||
elif age_days <= 90:
|
||||
status = 'aging'
|
||||
else:
|
||||
status = 'stale'
|
||||
|
||||
return {
|
||||
'status': status,
|
||||
'age_days': age_days,
|
||||
'last_updated': updated_at.isoformat() if hasattr(updated_at, 'isoformat') else str(updated_at)
|
||||
}
|
||||
except Exception:
|
||||
return {'status': 'unknown', 'age_days': 'unknown'}
|
||||
@@ -0,0 +1,39 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
REQUIRED_TOP_LEVEL_KEYS = {
|
||||
'fields': dict,
|
||||
'sources': dict,
|
||||
'quality_scores': dict,
|
||||
'confidence_levels': dict,
|
||||
'data_freshness': dict,
|
||||
'input_data_points': dict,
|
||||
}
|
||||
|
||||
|
||||
def validate_output(payload: Dict[str, Any]) -> None:
|
||||
# Top-level keys and types
|
||||
for key, typ in REQUIRED_TOP_LEVEL_KEYS.items():
|
||||
if key not in payload:
|
||||
raise ValueError(f"Autofill payload missing key: {key}")
|
||||
if not isinstance(payload[key], typ):
|
||||
raise ValueError(f"Autofill payload key '{key}' must be {typ.__name__}")
|
||||
|
||||
fields = payload['fields']
|
||||
if not isinstance(fields, dict):
|
||||
raise ValueError("fields must be an object")
|
||||
|
||||
# Allow empty fields, but validate structure when present
|
||||
for field_id, spec in fields.items():
|
||||
if not isinstance(spec, dict):
|
||||
raise ValueError(f"Field '{field_id}' must be an object")
|
||||
for k in ('value', 'source', 'confidence'):
|
||||
if k not in spec:
|
||||
raise ValueError(f"Field '{field_id}' missing '{k}'")
|
||||
if spec['source'] not in ('website_analysis', 'research_preferences', 'api_keys_data', 'onboarding_session'):
|
||||
raise ValueError(f"Field '{field_id}' has invalid source: {spec['source']}")
|
||||
try:
|
||||
c = float(spec['confidence'])
|
||||
except Exception:
|
||||
raise ValueError(f"Field '{field_id}' confidence must be numeric")
|
||||
if c < 0.0 or c > 1.0:
|
||||
raise ValueError(f"Field '{field_id}' confidence must be in [0,1]")
|
||||
@@ -0,0 +1,268 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
def transform_to_fields(*, website: Dict[str, Any], research: Dict[str, Any], api_keys: Dict[str, Any], session: Dict[str, Any]) -> Dict[str, Any]:
|
||||
fields: Dict[str, Any] = {}
|
||||
|
||||
# Business Context
|
||||
if website.get('content_goals'):
|
||||
fields['business_objectives'] = {
|
||||
'value': website.get('content_goals'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level')
|
||||
}
|
||||
|
||||
if website.get('target_metrics'):
|
||||
fields['target_metrics'] = {
|
||||
'value': website.get('target_metrics'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level')
|
||||
}
|
||||
elif website.get('performance_metrics'):
|
||||
fields['target_metrics'] = {
|
||||
'value': website.get('performance_metrics'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level')
|
||||
}
|
||||
|
||||
# content_budget with session fallback
|
||||
if website.get('content_budget') is not None:
|
||||
fields['content_budget'] = {
|
||||
'value': website.get('content_budget'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level')
|
||||
}
|
||||
elif isinstance(session, dict) and session.get('budget') is not None:
|
||||
fields['content_budget'] = {
|
||||
'value': session.get('budget'),
|
||||
'source': 'onboarding_session',
|
||||
'confidence': 0.7
|
||||
}
|
||||
|
||||
# team_size with session fallback
|
||||
if website.get('team_size') is not None:
|
||||
fields['team_size'] = {
|
||||
'value': website.get('team_size'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level')
|
||||
}
|
||||
elif isinstance(session, dict) and session.get('team_size') is not None:
|
||||
fields['team_size'] = {
|
||||
'value': session.get('team_size'),
|
||||
'source': 'onboarding_session',
|
||||
'confidence': 0.7
|
||||
}
|
||||
|
||||
# implementation_timeline with session fallback
|
||||
if website.get('implementation_timeline'):
|
||||
fields['implementation_timeline'] = {
|
||||
'value': website.get('implementation_timeline'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level')
|
||||
}
|
||||
elif isinstance(session, dict) and session.get('timeline'):
|
||||
fields['implementation_timeline'] = {
|
||||
'value': session.get('timeline'),
|
||||
'source': 'onboarding_session',
|
||||
'confidence': 0.7
|
||||
}
|
||||
|
||||
# market_share with derive from performance metrics
|
||||
if website.get('market_share'):
|
||||
fields['market_share'] = {
|
||||
'value': website.get('market_share'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level')
|
||||
}
|
||||
elif website.get('performance_metrics'):
|
||||
fields['market_share'] = {
|
||||
'value': website.get('performance_metrics', {}).get('estimated_market_share', None),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level')
|
||||
}
|
||||
|
||||
# performance metrics
|
||||
fields['performance_metrics'] = {
|
||||
'value': website.get('performance_metrics', {}),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
# Audience Intelligence
|
||||
audience_research = research.get('audience_intelligence', {})
|
||||
content_prefs = research.get('content_preferences', {})
|
||||
|
||||
fields['content_preferences'] = {
|
||||
'value': content_prefs,
|
||||
'source': 'research_preferences',
|
||||
'confidence': research.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['consumption_patterns'] = {
|
||||
'value': audience_research.get('consumption_patterns', {}),
|
||||
'source': 'research_preferences',
|
||||
'confidence': research.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['audience_pain_points'] = {
|
||||
'value': audience_research.get('pain_points', []),
|
||||
'source': 'research_preferences',
|
||||
'confidence': research.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['buying_journey'] = {
|
||||
'value': audience_research.get('buying_journey', {}),
|
||||
'source': 'research_preferences',
|
||||
'confidence': research.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['seasonal_trends'] = {
|
||||
'value': ['Q1: Planning', 'Q2: Execution', 'Q3: Optimization', 'Q4: Review'],
|
||||
'source': 'research_preferences',
|
||||
'confidence': research.get('confidence_level', 0.7)
|
||||
}
|
||||
|
||||
fields['engagement_metrics'] = {
|
||||
'value': {
|
||||
'avg_session_duration': website.get('performance_metrics', {}).get('avg_session_duration', 180),
|
||||
'bounce_rate': website.get('performance_metrics', {}).get('bounce_rate', 45.5),
|
||||
'pages_per_session': 2.5,
|
||||
},
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
# Competitive Intelligence
|
||||
fields['top_competitors'] = {
|
||||
'value': website.get('competitors', [
|
||||
'Competitor A - Industry Leader',
|
||||
'Competitor B - Emerging Player',
|
||||
'Competitor C - Niche Specialist'
|
||||
]),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['competitor_content_strategies'] = {
|
||||
'value': ['Educational content', 'Case studies', 'Thought leadership'],
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level', 0.7)
|
||||
}
|
||||
|
||||
fields['market_gaps'] = {
|
||||
'value': website.get('market_gaps', []),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['industry_trends'] = {
|
||||
'value': ['Digital transformation', 'AI/ML adoption', 'Remote work'],
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['emerging_trends'] = {
|
||||
'value': ['Voice search optimization', 'Video content', 'Interactive content'],
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level', 0.7)
|
||||
}
|
||||
|
||||
# Content Strategy
|
||||
fields['preferred_formats'] = {
|
||||
'value': content_prefs.get('preferred_formats', ['Blog posts', 'Whitepapers', 'Webinars', 'Case studies', 'Videos']),
|
||||
'source': 'research_preferences',
|
||||
'confidence': research.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['content_mix'] = {
|
||||
'value': {
|
||||
'blog_posts': 40,
|
||||
'whitepapers': 20,
|
||||
'webinars': 15,
|
||||
'case_studies': 15,
|
||||
'videos': 10,
|
||||
},
|
||||
'source': 'research_preferences',
|
||||
'confidence': research.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['content_frequency'] = {
|
||||
'value': 'Weekly',
|
||||
'source': 'research_preferences',
|
||||
'confidence': research.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['optimal_timing'] = {
|
||||
'value': {
|
||||
'best_days': ['Tuesday', 'Wednesday', 'Thursday'],
|
||||
'best_times': ['9:00 AM', '1:00 PM', '3:00 PM']
|
||||
},
|
||||
'source': 'research_preferences',
|
||||
'confidence': research.get('confidence_level', 0.7)
|
||||
}
|
||||
|
||||
fields['quality_metrics'] = {
|
||||
'value': {
|
||||
'readability_score': 8.5,
|
||||
'engagement_target': 5.0,
|
||||
'conversion_target': 2.0
|
||||
},
|
||||
'source': 'research_preferences',
|
||||
'confidence': research.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['editorial_guidelines'] = {
|
||||
'value': {
|
||||
'tone': content_prefs.get('content_style', ['Professional', 'Educational']),
|
||||
'length': content_prefs.get('content_length', 'Medium (1000-2000 words)'),
|
||||
'formatting': ['Use headers', 'Include visuals', 'Add CTAs']
|
||||
},
|
||||
'source': 'research_preferences',
|
||||
'confidence': research.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['brand_voice'] = {
|
||||
'value': {
|
||||
'tone': 'Professional yet approachable',
|
||||
'style': 'Educational and authoritative',
|
||||
'personality': 'Expert, helpful, trustworthy'
|
||||
},
|
||||
'source': 'research_preferences',
|
||||
'confidence': research.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
# Performance & Analytics
|
||||
fields['traffic_sources'] = {
|
||||
'value': website.get('traffic_sources', {}),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['conversion_rates'] = {
|
||||
'value': {
|
||||
'overall': website.get('performance_metrics', {}).get('conversion_rate', 3.2),
|
||||
'blog': 2.5,
|
||||
'landing_pages': 4.0,
|
||||
'email': 5.5,
|
||||
},
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['content_roi_targets'] = {
|
||||
'value': {
|
||||
'target_roi': 300,
|
||||
'cost_per_lead': 50,
|
||||
'lifetime_value': 500,
|
||||
},
|
||||
'source': 'website_analysis',
|
||||
'confidence': website.get('confidence_level', 0.7)
|
||||
}
|
||||
|
||||
fields['ab_testing_capabilities'] = {
|
||||
'value': True,
|
||||
'source': 'api_keys_data',
|
||||
'confidence': api_keys.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
return fields
|
||||
@@ -0,0 +1,98 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
def build_data_sources_map(website: Dict[str, Any], research: Dict[str, Any], api_keys: Dict[str, Any]) -> Dict[str, str]:
|
||||
sources: Dict[str, str] = {}
|
||||
|
||||
website_fields = ['business_objectives', 'target_metrics', 'content_budget', 'team_size',
|
||||
'implementation_timeline', 'market_share', 'competitive_position',
|
||||
'performance_metrics', 'engagement_metrics', 'top_competitors',
|
||||
'competitor_content_strategies', 'market_gaps', 'industry_trends',
|
||||
'emerging_trends', 'traffic_sources', 'conversion_rates', 'content_roi_targets']
|
||||
|
||||
research_fields = ['content_preferences', 'consumption_patterns', 'audience_pain_points',
|
||||
'buying_journey', 'seasonal_trends', 'preferred_formats', 'content_mix',
|
||||
'content_frequency', 'optimal_timing', 'quality_metrics', 'editorial_guidelines',
|
||||
'brand_voice']
|
||||
|
||||
api_fields = ['ab_testing_capabilities']
|
||||
|
||||
for f in website_fields:
|
||||
sources[f] = 'website_analysis'
|
||||
for f in research_fields:
|
||||
sources[f] = 'research_preferences'
|
||||
for f in api_fields:
|
||||
sources[f] = 'api_keys_data'
|
||||
|
||||
return sources
|
||||
|
||||
|
||||
def build_input_data_points(*, website_raw: Dict[str, Any], research_raw: Dict[str, Any], api_raw: Dict[str, Any]) -> Dict[str, Any]:
|
||||
input_data_points: Dict[str, Any] = {}
|
||||
|
||||
if website_raw:
|
||||
input_data_points['business_objectives'] = {
|
||||
'website_content': website_raw.get('content_goals', 'Not available'),
|
||||
'meta_description': website_raw.get('meta_description', 'Not available'),
|
||||
'about_page': website_raw.get('about_page_content', 'Not available'),
|
||||
'page_title': website_raw.get('page_title', 'Not available'),
|
||||
'content_analysis': website_raw.get('content_analysis', {})
|
||||
}
|
||||
|
||||
if research_raw:
|
||||
input_data_points['target_metrics'] = {
|
||||
'research_preferences': research_raw.get('target_audience', 'Not available'),
|
||||
'industry_benchmarks': research_raw.get('industry_benchmarks', 'Not available'),
|
||||
'competitor_analysis': research_raw.get('competitor_analysis', 'Not available'),
|
||||
'market_research': research_raw.get('market_research', 'Not available')
|
||||
}
|
||||
|
||||
if research_raw:
|
||||
input_data_points['content_preferences'] = {
|
||||
'user_preferences': research_raw.get('content_types', 'Not available'),
|
||||
'industry_trends': research_raw.get('industry_trends', 'Not available'),
|
||||
'consumption_patterns': research_raw.get('consumption_patterns', 'Not available'),
|
||||
'audience_research': research_raw.get('audience_research', 'Not available')
|
||||
}
|
||||
|
||||
if website_raw or research_raw:
|
||||
input_data_points['preferred_formats'] = {
|
||||
'existing_content': website_raw.get('existing_content_types', 'Not available') if website_raw else 'Not available',
|
||||
'engagement_metrics': website_raw.get('engagement_metrics', 'Not available') if website_raw else 'Not available',
|
||||
'platform_analysis': research_raw.get('platform_preferences', 'Not available') if research_raw else 'Not available',
|
||||
'content_performance': website_raw.get('content_performance', 'Not available') if website_raw else 'Not available'
|
||||
}
|
||||
|
||||
if research_raw:
|
||||
input_data_points['content_frequency'] = {
|
||||
'audience_research': research_raw.get('content_frequency_preferences', 'Not available'),
|
||||
'industry_standards': research_raw.get('industry_frequency', 'Not available'),
|
||||
'competitor_frequency': research_raw.get('competitor_frequency', 'Not available'),
|
||||
'optimal_timing': research_raw.get('optimal_timing', 'Not available')
|
||||
}
|
||||
|
||||
if website_raw:
|
||||
input_data_points['content_budget'] = {
|
||||
'website_analysis': website_raw.get('budget_indicators', 'Not available'),
|
||||
'industry_standards': website_raw.get('industry_budget', 'Not available'),
|
||||
'company_size': website_raw.get('company_size', 'Not available'),
|
||||
'market_position': website_raw.get('market_position', 'Not available')
|
||||
}
|
||||
|
||||
if website_raw:
|
||||
input_data_points['team_size'] = {
|
||||
'company_profile': website_raw.get('company_profile', 'Not available'),
|
||||
'content_volume': website_raw.get('content_volume', 'Not available'),
|
||||
'industry_standards': website_raw.get('industry_team_size', 'Not available'),
|
||||
'budget_constraints': website_raw.get('budget_constraints', 'Not available')
|
||||
}
|
||||
|
||||
if research_raw:
|
||||
input_data_points['implementation_timeline'] = {
|
||||
'project_scope': research_raw.get('project_scope', 'Not available'),
|
||||
'resource_availability': research_raw.get('resource_availability', 'Not available'),
|
||||
'industry_timeline': research_raw.get('industry_timeline', 'Not available'),
|
||||
'complexity_assessment': research_raw.get('complexity_assessment', 'Not available')
|
||||
}
|
||||
|
||||
return input_data_points
|
||||
@@ -0,0 +1,575 @@
|
||||
"""
|
||||
Transparency Service for Autofill Process
|
||||
Generates educational content and transparency messages for the strategy inputs autofill process.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from loguru import logger
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
class AutofillTransparencyService:
|
||||
"""Service for generating educational content and transparency messages during autofill process."""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def calculate_field_confidence_score(self, field_id: str, data_source: str, input_data: Any) -> float:
|
||||
"""Calculate confidence score for a specific field based on data quality and completeness."""
|
||||
|
||||
# Base confidence scores by data source
|
||||
source_confidence = {
|
||||
'website_analysis': 0.85,
|
||||
'research_preferences': 0.92,
|
||||
'api_keys': 0.78,
|
||||
'onboarding_session': 0.88,
|
||||
'unknown': 0.70
|
||||
}
|
||||
|
||||
base_confidence = source_confidence.get(data_source, 0.70)
|
||||
|
||||
# Adjust based on data completeness
|
||||
completeness_score = self._calculate_data_completeness(input_data)
|
||||
|
||||
# Adjust based on data freshness (if applicable)
|
||||
freshness_score = self._calculate_data_freshness(data_source)
|
||||
|
||||
# Adjust based on field-specific factors
|
||||
field_factor = self._get_field_specific_factor(field_id)
|
||||
|
||||
# Calculate final confidence score
|
||||
final_confidence = base_confidence * completeness_score * freshness_score * field_factor
|
||||
|
||||
# Ensure confidence is between 0.5 and 1.0
|
||||
return max(0.5, min(1.0, final_confidence))
|
||||
|
||||
def calculate_field_data_quality(self, field_id: str, data_source: str, input_data: Any) -> float:
|
||||
"""Calculate data quality score for a specific field."""
|
||||
|
||||
# Base quality scores by data source
|
||||
source_quality = {
|
||||
'website_analysis': 0.88,
|
||||
'research_preferences': 0.94,
|
||||
'api_keys': 0.82,
|
||||
'onboarding_session': 0.90,
|
||||
'unknown': 0.75
|
||||
}
|
||||
|
||||
base_quality = source_quality.get(data_source, 0.75)
|
||||
|
||||
# Adjust based on data structure and format
|
||||
structure_score = self._calculate_data_structure_quality(input_data)
|
||||
|
||||
# Adjust based on data consistency
|
||||
consistency_score = self._calculate_data_consistency(field_id, input_data)
|
||||
|
||||
# Adjust based on field-specific quality factors
|
||||
field_quality_factor = self._get_field_quality_factor(field_id)
|
||||
|
||||
# Calculate final quality score
|
||||
final_quality = base_quality * structure_score * consistency_score * field_quality_factor
|
||||
|
||||
# Ensure quality is between 0.6 and 1.0
|
||||
return max(0.6, min(1.0, final_quality))
|
||||
|
||||
def _calculate_data_completeness(self, input_data: Any) -> float:
|
||||
"""Calculate data completeness score."""
|
||||
if input_data is None:
|
||||
return 0.3
|
||||
|
||||
if isinstance(input_data, str):
|
||||
return 0.8 if len(input_data.strip()) > 10 else 0.5
|
||||
|
||||
if isinstance(input_data, (list, tuple)):
|
||||
return 0.9 if len(input_data) > 0 else 0.4
|
||||
|
||||
if isinstance(input_data, dict):
|
||||
# Check if dict has meaningful content
|
||||
if len(input_data) == 0:
|
||||
return 0.4
|
||||
# Check if values are not empty
|
||||
non_empty_values = sum(1 for v in input_data.values() if v and str(v).strip())
|
||||
return 0.7 + (0.2 * (non_empty_values / len(input_data)))
|
||||
|
||||
return 0.8
|
||||
|
||||
def _calculate_data_freshness(self, data_source: str) -> float:
|
||||
"""Calculate data freshness score."""
|
||||
# Mock freshness calculation - in real implementation, this would check timestamps
|
||||
freshness_scores = {
|
||||
'website_analysis': 0.95, # Usually recent
|
||||
'research_preferences': 0.90, # User-provided, recent
|
||||
'api_keys': 0.85, # Configuration data
|
||||
'onboarding_session': 0.92, # Recent user input
|
||||
'unknown': 0.80
|
||||
}
|
||||
return freshness_scores.get(data_source, 0.80)
|
||||
|
||||
def _calculate_data_structure_quality(self, input_data: Any) -> float:
|
||||
"""Calculate data structure quality score."""
|
||||
if input_data is None:
|
||||
return 0.5
|
||||
|
||||
if isinstance(input_data, str):
|
||||
# Check if string is well-formed
|
||||
if len(input_data.strip()) > 0:
|
||||
return 0.9
|
||||
return 0.6
|
||||
|
||||
if isinstance(input_data, (list, tuple)):
|
||||
# Check if list has proper structure
|
||||
if len(input_data) > 0:
|
||||
return 0.95
|
||||
return 0.7
|
||||
|
||||
if isinstance(input_data, dict):
|
||||
# Check if dict has proper structure
|
||||
if len(input_data) > 0:
|
||||
return 0.92
|
||||
return 0.6
|
||||
|
||||
return 0.8
|
||||
|
||||
def _calculate_data_consistency(self, field_id: str, input_data: Any) -> float:
|
||||
"""Calculate data consistency score."""
|
||||
# Mock consistency calculation - in real implementation, this would check against expected formats
|
||||
if input_data is None:
|
||||
return 0.6
|
||||
|
||||
# Field-specific consistency checks
|
||||
consistency_factors = {
|
||||
'business_objectives': 0.95,
|
||||
'target_metrics': 0.92,
|
||||
'content_budget': 0.88,
|
||||
'team_size': 0.90,
|
||||
'implementation_timeline': 0.85,
|
||||
'market_share': 0.87,
|
||||
'competitive_position': 0.89,
|
||||
'performance_metrics': 0.91,
|
||||
'content_preferences': 0.93,
|
||||
'consumption_patterns': 0.90,
|
||||
'audience_pain_points': 0.88,
|
||||
'buying_journey': 0.89,
|
||||
'seasonal_trends': 0.86,
|
||||
'engagement_metrics': 0.92,
|
||||
'top_competitors': 0.90,
|
||||
'competitor_content_strategies': 0.87,
|
||||
'market_gaps': 0.85,
|
||||
'industry_trends': 0.88,
|
||||
'emerging_trends': 0.84,
|
||||
'preferred_formats': 0.93,
|
||||
'content_mix': 0.89,
|
||||
'content_frequency': 0.91,
|
||||
'optimal_timing': 0.88,
|
||||
'quality_metrics': 0.90,
|
||||
'editorial_guidelines': 0.87,
|
||||
'brand_voice': 0.89,
|
||||
'traffic_sources': 0.92,
|
||||
'conversion_rates': 0.88,
|
||||
'content_roi_targets': 0.86,
|
||||
'ab_testing_capabilities': 0.90
|
||||
}
|
||||
|
||||
return consistency_factors.get(field_id, 0.85)
|
||||
|
||||
def _get_field_specific_factor(self, field_id: str) -> float:
|
||||
"""Get field-specific confidence factor."""
|
||||
# Some fields are inherently more reliable than others
|
||||
field_factors = {
|
||||
'business_objectives': 1.0, # High confidence
|
||||
'target_metrics': 0.95,
|
||||
'content_budget': 0.90,
|
||||
'team_size': 0.92,
|
||||
'implementation_timeline': 0.88,
|
||||
'market_share': 0.85,
|
||||
'competitive_position': 0.87,
|
||||
'performance_metrics': 0.93,
|
||||
'content_preferences': 0.96, # User-provided, high confidence
|
||||
'consumption_patterns': 0.89,
|
||||
'audience_pain_points': 0.86,
|
||||
'buying_journey': 0.88,
|
||||
'seasonal_trends': 0.84,
|
||||
'engagement_metrics': 0.91,
|
||||
'top_competitors': 0.89,
|
||||
'competitor_content_strategies': 0.85,
|
||||
'market_gaps': 0.83,
|
||||
'industry_trends': 0.87,
|
||||
'emerging_trends': 0.82,
|
||||
'preferred_formats': 0.94,
|
||||
'content_mix': 0.88,
|
||||
'content_frequency': 0.90,
|
||||
'optimal_timing': 0.86,
|
||||
'quality_metrics': 0.89,
|
||||
'editorial_guidelines': 0.85,
|
||||
'brand_voice': 0.87,
|
||||
'traffic_sources': 0.91,
|
||||
'conversion_rates': 0.88,
|
||||
'content_roi_targets': 0.85,
|
||||
'ab_testing_capabilities': 0.89
|
||||
}
|
||||
|
||||
return field_factors.get(field_id, 0.85)
|
||||
|
||||
def _get_field_quality_factor(self, field_id: str) -> float:
|
||||
"""Get field-specific quality factor."""
|
||||
# Quality factors based on data complexity and reliability
|
||||
quality_factors = {
|
||||
'business_objectives': 0.95,
|
||||
'target_metrics': 0.93,
|
||||
'content_budget': 0.90,
|
||||
'team_size': 0.92,
|
||||
'implementation_timeline': 0.88,
|
||||
'market_share': 0.86,
|
||||
'competitive_position': 0.89,
|
||||
'performance_metrics': 0.94,
|
||||
'content_preferences': 0.96,
|
||||
'consumption_patterns': 0.91,
|
||||
'audience_pain_points': 0.87,
|
||||
'buying_journey': 0.89,
|
||||
'seasonal_trends': 0.85,
|
||||
'engagement_metrics': 0.93,
|
||||
'top_competitors': 0.90,
|
||||
'competitor_content_strategies': 0.86,
|
||||
'market_gaps': 0.84,
|
||||
'industry_trends': 0.88,
|
||||
'emerging_trends': 0.83,
|
||||
'preferred_formats': 0.95,
|
||||
'content_mix': 0.89,
|
||||
'content_frequency': 0.91,
|
||||
'optimal_timing': 0.87,
|
||||
'quality_metrics': 0.92,
|
||||
'editorial_guidelines': 0.86,
|
||||
'brand_voice': 0.88,
|
||||
'traffic_sources': 0.93,
|
||||
'conversion_rates': 0.89,
|
||||
'content_roi_targets': 0.86,
|
||||
'ab_testing_capabilities': 0.90
|
||||
}
|
||||
|
||||
return quality_factors.get(field_id, 0.87)
|
||||
|
||||
def get_field_mapping_with_metrics(self, auto_populated_fields: Dict[str, Any], data_sources: Dict[str, str], input_data_points: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Get field mapping with confidence scores and data quality metrics."""
|
||||
|
||||
field_categories = {
|
||||
'Business Context': [
|
||||
'business_objectives', 'target_metrics', 'content_budget', 'team_size',
|
||||
'implementation_timeline', 'market_share', 'competitive_position', 'performance_metrics'
|
||||
],
|
||||
'Audience Intelligence': [
|
||||
'content_preferences', 'consumption_patterns', 'audience_pain_points',
|
||||
'buying_journey', 'seasonal_trends', 'engagement_metrics'
|
||||
],
|
||||
'Competitive Intelligence': [
|
||||
'top_competitors', 'competitor_content_strategies', 'market_gaps',
|
||||
'industry_trends', 'emerging_trends'
|
||||
],
|
||||
'Content Strategy': [
|
||||
'preferred_formats', 'content_mix', 'content_frequency', 'optimal_timing',
|
||||
'quality_metrics', 'editorial_guidelines', 'brand_voice'
|
||||
],
|
||||
'Performance & Analytics': [
|
||||
'traffic_sources', 'conversion_rates', 'content_roi_targets', 'ab_testing_capabilities'
|
||||
]
|
||||
}
|
||||
|
||||
result = []
|
||||
|
||||
for category_name, field_ids in field_categories.items():
|
||||
category_fields = []
|
||||
|
||||
for field_id in field_ids:
|
||||
data_source = data_sources.get(field_id, 'unknown')
|
||||
input_data = input_data_points.get(field_id)
|
||||
field_value = auto_populated_fields.get(field_id)
|
||||
|
||||
# Calculate real confidence and quality scores
|
||||
confidence_score = self.calculate_field_confidence_score(field_id, data_source, input_data)
|
||||
data_quality_score = self.calculate_field_data_quality(field_id, data_source, input_data)
|
||||
|
||||
category_fields.append({
|
||||
'fieldId': field_id,
|
||||
'label': field_id.replace('_', ' ').title(),
|
||||
'source': data_source,
|
||||
'value': field_value,
|
||||
'confidence': confidence_score,
|
||||
'dataQuality': data_quality_score,
|
||||
'inputData': input_data
|
||||
})
|
||||
|
||||
result.append({
|
||||
'category': category_name,
|
||||
'fields': category_fields
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
def get_phase_educational_content(self, phase: str, context: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
"""Generate educational content for a specific phase of the autofill process."""
|
||||
|
||||
educational_content = {
|
||||
'title': '',
|
||||
'description': '',
|
||||
'points': [],
|
||||
'tips': [],
|
||||
'phase': phase,
|
||||
'timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
if phase == 'autofill_initialization':
|
||||
educational_content.update({
|
||||
'title': 'Initializing Strategy Inputs Generation',
|
||||
'description': 'We\'re preparing to analyze your data and generate personalized strategy inputs.',
|
||||
'points': [
|
||||
'Analyzing your business context and industry data',
|
||||
'Preparing AI models for strategy input generation',
|
||||
'Setting up data quality assessment frameworks',
|
||||
'Initializing transparency and educational content systems'
|
||||
],
|
||||
'tips': [
|
||||
'This phase ensures all systems are ready for optimal generation',
|
||||
'The initialization process adapts to your specific business context',
|
||||
'We\'ll provide real-time transparency throughout the entire process'
|
||||
]
|
||||
})
|
||||
|
||||
elif phase == 'autofill_data_collection':
|
||||
educational_content.update({
|
||||
'title': 'Collecting and Analyzing Data Sources',
|
||||
'description': 'We\'re gathering and analyzing all available data sources to inform your strategy inputs.',
|
||||
'points': [
|
||||
'Retrieving your website analysis and content insights',
|
||||
'Analyzing competitor data and market positioning',
|
||||
'Processing research preferences and target audience data',
|
||||
'Integrating API configurations and external data sources'
|
||||
],
|
||||
'tips': [
|
||||
'More comprehensive data leads to more accurate strategy inputs',
|
||||
'We prioritize data quality over quantity for better results',
|
||||
'All data sources are analyzed for relevance and reliability'
|
||||
]
|
||||
})
|
||||
|
||||
elif phase == 'autofill_data_quality':
|
||||
educational_content.update({
|
||||
'title': 'Assessing Data Quality and Completeness',
|
||||
'description': 'We\'re evaluating the quality and completeness of your data to ensure optimal strategy generation.',
|
||||
'points': [
|
||||
'Evaluating data freshness and relevance',
|
||||
'Assessing completeness of business context information',
|
||||
'Analyzing data consistency across different sources',
|
||||
'Identifying potential data gaps and opportunities'
|
||||
],
|
||||
'tips': [
|
||||
'High-quality data ensures more accurate and actionable strategy inputs',
|
||||
'We\'ll highlight any data gaps that could impact strategy quality',
|
||||
'Data quality scores help you understand confidence levels'
|
||||
]
|
||||
})
|
||||
|
||||
elif phase == 'autofill_context_analysis':
|
||||
educational_content.update({
|
||||
'title': 'Analyzing Business Context and Strategic Framework',
|
||||
'description': 'We\'re analyzing your business context to create a strategic framework for content planning.',
|
||||
'points': [
|
||||
'Understanding your business objectives and goals',
|
||||
'Analyzing market position and competitive landscape',
|
||||
'Evaluating target audience and customer journey',
|
||||
'Identifying content opportunities and strategic priorities'
|
||||
],
|
||||
'tips': [
|
||||
'This analysis forms the foundation for all strategy inputs',
|
||||
'We consider both internal and external factors',
|
||||
'The framework adapts to your specific industry and business model'
|
||||
]
|
||||
})
|
||||
|
||||
elif phase == 'autofill_strategy_generation':
|
||||
educational_content.update({
|
||||
'title': 'Generating Strategic Insights and Recommendations',
|
||||
'description': 'We\'re generating strategic insights and recommendations based on your data analysis.',
|
||||
'points': [
|
||||
'Creating strategic insights from analyzed data',
|
||||
'Generating actionable recommendations for content strategy',
|
||||
'Identifying key opportunities and competitive advantages',
|
||||
'Developing strategic priorities and focus areas'
|
||||
],
|
||||
'tips': [
|
||||
'Strategic insights are tailored to your specific business context',
|
||||
'Recommendations are actionable and measurable',
|
||||
'We focus on opportunities that align with your business objectives'
|
||||
]
|
||||
})
|
||||
|
||||
elif phase == 'autofill_field_generation':
|
||||
educational_content.update({
|
||||
'title': 'Generating Individual Strategy Input Fields',
|
||||
'description': 'We\'re generating specific strategy input fields based on your data and strategic analysis.',
|
||||
'points': [
|
||||
'Generating business context and objectives',
|
||||
'Creating audience intelligence and insights',
|
||||
'Developing competitive intelligence and positioning',
|
||||
'Formulating content strategy and performance metrics'
|
||||
],
|
||||
'tips': [
|
||||
'Each field is generated with confidence scores and quality metrics',
|
||||
'Fields are validated for consistency and alignment',
|
||||
'You can review and modify any generated field'
|
||||
]
|
||||
})
|
||||
|
||||
elif phase == 'autofill_quality_validation':
|
||||
educational_content.update({
|
||||
'title': 'Validating Generated Strategy Inputs',
|
||||
'description': 'We\'re validating all generated strategy inputs for quality, consistency, and alignment.',
|
||||
'points': [
|
||||
'Checking data quality and completeness',
|
||||
'Validating field consistency and alignment',
|
||||
'Ensuring strategic coherence across all inputs',
|
||||
'Identifying any potential issues or improvements'
|
||||
],
|
||||
'tips': [
|
||||
'Quality validation ensures reliable and actionable strategy inputs',
|
||||
'We check for consistency across all generated fields',
|
||||
'Any issues are flagged for your review and consideration'
|
||||
]
|
||||
})
|
||||
|
||||
elif phase == 'autofill_alignment_check':
|
||||
educational_content.update({
|
||||
'title': 'Checking Strategy Alignment and Consistency',
|
||||
'description': 'We\'re ensuring all strategy inputs are aligned and consistent with your business objectives.',
|
||||
'points': [
|
||||
'Verifying alignment with business objectives',
|
||||
'Checking consistency across strategic inputs',
|
||||
'Ensuring coherence with market positioning',
|
||||
'Validating strategic priorities and focus areas'
|
||||
],
|
||||
'tips': [
|
||||
'Alignment ensures all strategy inputs work together effectively',
|
||||
'Consistency prevents conflicting strategic directions',
|
||||
'Strategic coherence maximizes the impact of your content strategy'
|
||||
]
|
||||
})
|
||||
|
||||
elif phase == 'autofill_final_review':
|
||||
educational_content.update({
|
||||
'title': 'Performing Final Review and Optimization',
|
||||
'description': 'We\'re conducting a final review and optimization of all strategy inputs.',
|
||||
'points': [
|
||||
'Reviewing all generated strategy inputs',
|
||||
'Optimizing for maximum strategic impact',
|
||||
'Ensuring all inputs are actionable and measurable',
|
||||
'Preparing final strategy input recommendations'
|
||||
],
|
||||
'tips': [
|
||||
'Final review ensures optimal quality and strategic value',
|
||||
'Optimization maximizes the effectiveness of your strategy',
|
||||
'All inputs are ready for immediate implementation'
|
||||
]
|
||||
})
|
||||
|
||||
elif phase == 'autofill_complete':
|
||||
educational_content.update({
|
||||
'title': 'Strategy Inputs Generation Completed Successfully',
|
||||
'description': 'Your strategy inputs have been generated successfully with comprehensive transparency and quality assurance.',
|
||||
'points': [
|
||||
'All 30 strategy input fields have been generated',
|
||||
'Quality validation and alignment checks completed',
|
||||
'Confidence scores and data quality metrics provided',
|
||||
'Strategy inputs ready for implementation and review'
|
||||
],
|
||||
'tips': [
|
||||
'Review the generated inputs and modify as needed',
|
||||
'Use confidence scores to prioritize high-quality inputs',
|
||||
'The transparency data helps you understand data source influence'
|
||||
]
|
||||
})
|
||||
|
||||
return educational_content
|
||||
|
||||
def get_transparency_message(self, phase: str, context: Dict[str, Any] = None) -> str:
|
||||
"""Generate a transparency message for a specific phase."""
|
||||
|
||||
messages = {
|
||||
'autofill_initialization': 'Starting strategy inputs generation process...',
|
||||
'autofill_data_collection': 'Collecting and analyzing data sources from your onboarding and research...',
|
||||
'autofill_data_quality': 'Assessing data quality and completeness for optimal strategy generation...',
|
||||
'autofill_context_analysis': 'Analyzing your business context and creating strategic framework...',
|
||||
'autofill_strategy_generation': 'Generating strategic insights and recommendations using AI...',
|
||||
'autofill_field_generation': 'Generating individual strategy input fields based on your data...',
|
||||
'autofill_quality_validation': 'Validating generated strategy inputs for quality and consistency...',
|
||||
'autofill_alignment_check': 'Checking strategy alignment and consistency across all inputs...',
|
||||
'autofill_final_review': 'Performing final review and optimization of strategy inputs...',
|
||||
'autofill_complete': 'Strategy inputs generation completed successfully!'
|
||||
}
|
||||
|
||||
base_message = messages.get(phase, f'Processing phase: {phase}')
|
||||
|
||||
# Add context-specific details if available
|
||||
if context and 'data_sources' in context:
|
||||
data_sources = context['data_sources']
|
||||
if data_sources:
|
||||
source_count = len(data_sources)
|
||||
base_message += f' (Analyzing {source_count} data sources)'
|
||||
|
||||
return base_message
|
||||
|
||||
def get_data_source_summary(self, base_context: Dict[str, Any]) -> Dict[str, List[str]]:
|
||||
"""Get a summary of data sources and their associated fields."""
|
||||
|
||||
# Extract data sources from base context
|
||||
data_sources = {}
|
||||
|
||||
# Website analysis fields
|
||||
website_fields = ['business_objectives', 'target_metrics', 'content_budget', 'team_size',
|
||||
'implementation_timeline', 'market_share', 'competitive_position',
|
||||
'performance_metrics', 'engagement_metrics', 'top_competitors',
|
||||
'competitor_content_strategies', 'market_gaps', 'industry_trends',
|
||||
'emerging_trends', 'traffic_sources', 'conversion_rates', 'content_roi_targets']
|
||||
|
||||
# Research preferences fields
|
||||
research_fields = ['content_preferences', 'consumption_patterns', 'audience_pain_points',
|
||||
'buying_journey', 'seasonal_trends', 'preferred_formats', 'content_mix',
|
||||
'content_frequency', 'optimal_timing', 'quality_metrics', 'editorial_guidelines',
|
||||
'brand_voice']
|
||||
|
||||
# API configuration fields
|
||||
api_fields = ['ab_testing_capabilities']
|
||||
|
||||
# Onboarding session fields (fallback for any remaining fields)
|
||||
onboarding_fields = []
|
||||
|
||||
# Map fields to data sources
|
||||
for field in website_fields:
|
||||
data_sources[field] = 'website_analysis'
|
||||
|
||||
for field in research_fields:
|
||||
data_sources[field] = 'research_preferences'
|
||||
|
||||
for field in api_fields:
|
||||
data_sources[field] = 'api_keys'
|
||||
|
||||
# Group fields by data source
|
||||
source_summary = {}
|
||||
for field, source in data_sources.items():
|
||||
if source not in source_summary:
|
||||
source_summary[source] = []
|
||||
source_summary[source].append(field)
|
||||
|
||||
return source_summary
|
||||
|
||||
def generate_phase_message(self, phase: str, context: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
"""Generate a complete phase message with transparency information."""
|
||||
|
||||
message = self.get_transparency_message(phase, context)
|
||||
educational_content = self.get_phase_educational_content(phase, context)
|
||||
|
||||
return {
|
||||
'type': phase,
|
||||
'message': message,
|
||||
'educational_content': educational_content,
|
||||
'timestamp': datetime.utcnow().isoformat(),
|
||||
'context': context or {}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
Core Module
|
||||
Core strategy service and essential components.
|
||||
"""
|
||||
|
||||
from .strategy_service import EnhancedStrategyService
|
||||
from .field_mappings import STRATEGIC_INPUT_FIELDS
|
||||
from .constants import SERVICE_CONSTANTS
|
||||
|
||||
__all__ = [
|
||||
'EnhancedStrategyService',
|
||||
'STRATEGIC_INPUT_FIELDS',
|
||||
'SERVICE_CONSTANTS'
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
Service Constants for Content Strategy
|
||||
Configuration and settings for the enhanced strategy service.
|
||||
"""
|
||||
|
||||
# Performance optimization settings
|
||||
PROMPT_VERSIONS = {
|
||||
'comprehensive_strategy': 'v2.1',
|
||||
'audience_intelligence': 'v2.0',
|
||||
'competitive_intelligence': 'v2.0',
|
||||
'performance_optimization': 'v2.1',
|
||||
'content_calendar_optimization': 'v2.0'
|
||||
}
|
||||
|
||||
QUALITY_THRESHOLDS = {
|
||||
'min_confidence': 0.7,
|
||||
'min_completeness': 0.8,
|
||||
'max_response_time': 30.0 # seconds
|
||||
}
|
||||
|
||||
CACHE_SETTINGS = {
|
||||
'ai_analysis_cache_ttl': 3600, # 1 hour
|
||||
'onboarding_data_cache_ttl': 1800, # 30 minutes
|
||||
'strategy_cache_ttl': 7200, # 2 hours
|
||||
'max_cache_size': 1000 # Maximum cached items
|
||||
}
|
||||
|
||||
# Service constants
|
||||
SERVICE_CONSTANTS = {
|
||||
'prompt_versions': PROMPT_VERSIONS,
|
||||
'quality_thresholds': QUALITY_THRESHOLDS,
|
||||
'cache_settings': CACHE_SETTINGS
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Strategic Input Field Mappings
|
||||
Definitions for the 30+ strategic input fields.
|
||||
"""
|
||||
|
||||
# Define the 30+ strategic input fields
|
||||
STRATEGIC_INPUT_FIELDS = {
|
||||
'business_context': [
|
||||
'business_objectives', 'target_metrics', 'content_budget', 'team_size',
|
||||
'implementation_timeline', 'market_share', 'competitive_position', 'performance_metrics'
|
||||
],
|
||||
'audience_intelligence': [
|
||||
'content_preferences', 'consumption_patterns', 'audience_pain_points',
|
||||
'buying_journey', 'seasonal_trends', 'engagement_metrics'
|
||||
],
|
||||
'competitive_intelligence': [
|
||||
'top_competitors', 'competitor_content_strategies', 'market_gaps',
|
||||
'industry_trends', 'emerging_trends'
|
||||
],
|
||||
'content_strategy': [
|
||||
'preferred_formats', 'content_mix', 'content_frequency', 'optimal_timing',
|
||||
'quality_metrics', 'editorial_guidelines', 'brand_voice'
|
||||
],
|
||||
'performance_analytics': [
|
||||
'traffic_sources', 'conversion_rates', 'content_roi_targets', 'ab_testing_capabilities'
|
||||
]
|
||||
}
|
||||
|
||||
# Field categories for organization
|
||||
FIELD_CATEGORIES = {
|
||||
'business_context': {
|
||||
'name': 'Business Context',
|
||||
'description': 'Core business objectives and metrics',
|
||||
'fields': STRATEGIC_INPUT_FIELDS['business_context']
|
||||
},
|
||||
'audience_intelligence': {
|
||||
'name': 'Audience Intelligence',
|
||||
'description': 'Target audience analysis and insights',
|
||||
'fields': STRATEGIC_INPUT_FIELDS['audience_intelligence']
|
||||
},
|
||||
'competitive_intelligence': {
|
||||
'name': 'Competitive Intelligence',
|
||||
'description': 'Competitor analysis and market positioning',
|
||||
'fields': STRATEGIC_INPUT_FIELDS['competitive_intelligence']
|
||||
},
|
||||
'content_strategy': {
|
||||
'name': 'Content Strategy',
|
||||
'description': 'Content planning and execution',
|
||||
'fields': STRATEGIC_INPUT_FIELDS['content_strategy']
|
||||
},
|
||||
'performance_analytics': {
|
||||
'name': 'Performance & Analytics',
|
||||
'description': 'Performance tracking and optimization',
|
||||
'fields': STRATEGIC_INPUT_FIELDS['performance_analytics']
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,569 @@
|
||||
"""
|
||||
Enhanced Strategy Service - Core Module
|
||||
Main orchestration service for content strategy operations.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any, Optional, List, Union
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
# Import database models
|
||||
from models.enhanced_strategy_models import EnhancedContentStrategy, EnhancedAIAnalysisResult, OnboardingDataIntegration
|
||||
from models.onboarding import OnboardingSession, WebsiteAnalysis, ResearchPreferences, APIKey
|
||||
|
||||
# Import modular services
|
||||
from ..ai_analysis.ai_recommendations import AIRecommendationsService
|
||||
from ..ai_analysis.prompt_engineering import PromptEngineeringService
|
||||
from ..ai_analysis.quality_validation import QualityValidationService
|
||||
from ..ai_analysis.strategy_analyzer import StrategyAnalyzer
|
||||
|
||||
# Import onboarding services
|
||||
from ..onboarding.data_integration import OnboardingDataIntegrationService
|
||||
from ..onboarding.field_transformation import FieldTransformationService
|
||||
from ..onboarding.data_quality import DataQualityService
|
||||
|
||||
# Import performance services
|
||||
from ..performance.caching import CachingService
|
||||
from ..performance.optimization import PerformanceOptimizationService
|
||||
from ..performance.health_monitoring import HealthMonitoringService
|
||||
|
||||
# Import utils services
|
||||
from ..utils.data_processors import DataProcessorService
|
||||
from ..utils.validators import ValidationService
|
||||
from ..utils.strategy_utils import (
|
||||
extract_content_preferences_from_style,
|
||||
extract_brand_voice_from_guidelines,
|
||||
extract_editorial_guidelines_from_style,
|
||||
create_field_mappings,
|
||||
calculate_data_quality_scores
|
||||
)
|
||||
|
||||
# Import core components
|
||||
from .field_mappings import STRATEGIC_INPUT_FIELDS
|
||||
from .constants import SERVICE_CONSTANTS
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EnhancedStrategyService:
|
||||
"""Enhanced content strategy service with modular architecture."""
|
||||
|
||||
def __init__(self, db_service: Optional[Any] = None):
|
||||
# Store db_service for compatibility
|
||||
self.db_service = db_service
|
||||
|
||||
# Initialize AI analysis services
|
||||
self.ai_recommendations_service = AIRecommendationsService()
|
||||
self.prompt_engineering_service = PromptEngineeringService()
|
||||
self.quality_validation_service = QualityValidationService()
|
||||
self.strategy_analyzer = StrategyAnalyzer()
|
||||
|
||||
# Initialize onboarding services
|
||||
self.onboarding_data_service = OnboardingDataIntegrationService()
|
||||
self.field_transformation_service = FieldTransformationService()
|
||||
self.data_quality_service = DataQualityService()
|
||||
|
||||
# Initialize performance services
|
||||
self.caching_service = CachingService()
|
||||
self.performance_optimization_service = PerformanceOptimizationService()
|
||||
self.health_monitoring_service = HealthMonitoringService()
|
||||
|
||||
# Initialize utils services
|
||||
self.data_processor_service = DataProcessorService()
|
||||
self.validation_service = ValidationService()
|
||||
|
||||
async def create_enhanced_strategy(self, strategy_data: Dict[str, Any], db: Session) -> Dict[str, Any]:
|
||||
"""Create a new enhanced content strategy with 30+ strategic inputs."""
|
||||
try:
|
||||
logger.info(f"Creating enhanced content strategy: {strategy_data.get('name', 'Unknown')}")
|
||||
|
||||
# Extract user_id from strategy_data
|
||||
user_id = strategy_data.get('user_id')
|
||||
if not user_id:
|
||||
raise ValueError("user_id is required for creating enhanced strategy")
|
||||
|
||||
# Create the enhanced strategy
|
||||
enhanced_strategy = EnhancedContentStrategy(
|
||||
user_id=user_id,
|
||||
name=strategy_data.get('name', 'Enhanced Content Strategy'),
|
||||
industry=strategy_data.get('industry'),
|
||||
|
||||
# Business Context
|
||||
business_objectives=strategy_data.get('business_objectives'),
|
||||
target_metrics=strategy_data.get('target_metrics'),
|
||||
content_budget=strategy_data.get('content_budget'),
|
||||
team_size=strategy_data.get('team_size'),
|
||||
implementation_timeline=strategy_data.get('implementation_timeline'),
|
||||
market_share=strategy_data.get('market_share'),
|
||||
competitive_position=strategy_data.get('competitive_position'),
|
||||
performance_metrics=strategy_data.get('performance_metrics'),
|
||||
|
||||
# Audience Intelligence
|
||||
content_preferences=strategy_data.get('content_preferences'),
|
||||
consumption_patterns=strategy_data.get('consumption_patterns'),
|
||||
audience_pain_points=strategy_data.get('audience_pain_points'),
|
||||
buying_journey=strategy_data.get('buying_journey'),
|
||||
seasonal_trends=strategy_data.get('seasonal_trends'),
|
||||
engagement_metrics=strategy_data.get('engagement_metrics'),
|
||||
|
||||
# Competitive Intelligence
|
||||
top_competitors=strategy_data.get('top_competitors'),
|
||||
competitor_content_strategies=strategy_data.get('competitor_content_strategies'),
|
||||
market_gaps=strategy_data.get('market_gaps'),
|
||||
industry_trends=strategy_data.get('industry_trends'),
|
||||
emerging_trends=strategy_data.get('emerging_trends'),
|
||||
|
||||
# Content Strategy
|
||||
preferred_formats=strategy_data.get('preferred_formats'),
|
||||
content_mix=strategy_data.get('content_mix'),
|
||||
content_frequency=strategy_data.get('content_frequency'),
|
||||
optimal_timing=strategy_data.get('optimal_timing'),
|
||||
quality_metrics=strategy_data.get('quality_metrics'),
|
||||
editorial_guidelines=strategy_data.get('editorial_guidelines'),
|
||||
brand_voice=strategy_data.get('brand_voice'),
|
||||
|
||||
# Performance & Analytics
|
||||
traffic_sources=strategy_data.get('traffic_sources'),
|
||||
conversion_rates=strategy_data.get('conversion_rates'),
|
||||
content_roi_targets=strategy_data.get('content_roi_targets'),
|
||||
ab_testing_capabilities=strategy_data.get('ab_testing_capabilities', False),
|
||||
|
||||
# Legacy fields
|
||||
target_audience=strategy_data.get('target_audience'),
|
||||
content_pillars=strategy_data.get('content_pillars'),
|
||||
ai_recommendations=strategy_data.get('ai_recommendations')
|
||||
)
|
||||
|
||||
# Calculate completion percentage
|
||||
enhanced_strategy.calculate_completion_percentage()
|
||||
|
||||
# Add to database
|
||||
db.add(enhanced_strategy)
|
||||
db.commit()
|
||||
db.refresh(enhanced_strategy)
|
||||
|
||||
# Integrate onboarding data if available
|
||||
await self._enhance_strategy_with_onboarding_data(enhanced_strategy, user_id, db)
|
||||
|
||||
# Generate comprehensive AI recommendations
|
||||
try:
|
||||
# Generate AI recommendations without timeout (allow natural processing time)
|
||||
await self.strategy_analyzer.generate_comprehensive_ai_recommendations(enhanced_strategy, db)
|
||||
logger.info(f"✅ AI recommendations generated successfully for strategy: {enhanced_strategy.id}")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ AI recommendations generation failed for strategy: {enhanced_strategy.id}: {str(e)} - continuing without AI recommendations")
|
||||
# Continue without AI recommendations
|
||||
|
||||
# Cache the strategy
|
||||
await self.caching_service.cache_strategy(enhanced_strategy.id, enhanced_strategy.to_dict())
|
||||
|
||||
logger.info(f"✅ Enhanced strategy created successfully: {enhanced_strategy.id}")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Enhanced content strategy created successfully",
|
||||
"strategy": enhanced_strategy.to_dict(),
|
||||
"strategy_id": enhanced_strategy.id,
|
||||
"completion_percentage": enhanced_strategy.completion_percentage
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error creating enhanced strategy: {str(e)}")
|
||||
db.rollback()
|
||||
raise
|
||||
|
||||
async def get_enhanced_strategies(self, user_id: Optional[int] = None, strategy_id: Optional[int] = None, db: Session = None) -> Dict[str, Any]:
|
||||
"""Get enhanced content strategies with comprehensive data and AI recommendations."""
|
||||
try:
|
||||
logger.info(f"🚀 Starting enhanced strategy analysis for user: {user_id}, strategy: {strategy_id}")
|
||||
|
||||
# Use db_service if available, otherwise use direct db
|
||||
if self.db_service and hasattr(self.db_service, 'db'):
|
||||
# Use db_service methods
|
||||
if strategy_id:
|
||||
strategy = await self.db_service.get_enhanced_strategy(strategy_id)
|
||||
strategies = [strategy] if strategy else []
|
||||
else:
|
||||
strategies = await self.db_service.get_enhanced_strategies(user_id)
|
||||
else:
|
||||
# Fallback to direct db access
|
||||
if not db:
|
||||
raise ValueError("Database session is required when db_service is not available")
|
||||
|
||||
# Build query
|
||||
query = db.query(EnhancedContentStrategy)
|
||||
|
||||
if user_id:
|
||||
query = query.filter(EnhancedContentStrategy.user_id == user_id)
|
||||
|
||||
if strategy_id:
|
||||
query = query.filter(EnhancedContentStrategy.id == strategy_id)
|
||||
|
||||
# Get strategies
|
||||
strategies = query.all()
|
||||
|
||||
if not strategies:
|
||||
logger.warning("⚠️ No enhanced strategies found")
|
||||
return {
|
||||
"status": "not_found",
|
||||
"message": "No enhanced content strategies found",
|
||||
"strategies": [],
|
||||
"total_count": 0,
|
||||
"user_id": user_id
|
||||
}
|
||||
|
||||
# Process each strategy
|
||||
enhanced_strategies = []
|
||||
for strategy in strategies:
|
||||
# Calculate completion percentage
|
||||
if hasattr(strategy, 'calculate_completion_percentage'):
|
||||
strategy.calculate_completion_percentage()
|
||||
|
||||
# Get AI analysis results
|
||||
ai_analysis = await self.strategy_analyzer.get_latest_ai_analysis(strategy.id, db) if db else None
|
||||
|
||||
# Get onboarding data integration
|
||||
onboarding_integration = await self.strategy_analyzer.get_onboarding_integration(strategy.id, db) if db else None
|
||||
|
||||
strategy_dict = strategy.to_dict() if hasattr(strategy, 'to_dict') else {
|
||||
'id': strategy.id,
|
||||
'name': strategy.name,
|
||||
'industry': strategy.industry,
|
||||
'user_id': strategy.user_id,
|
||||
'created_at': strategy.created_at.isoformat() if strategy.created_at else None,
|
||||
'updated_at': strategy.updated_at.isoformat() if strategy.updated_at else None
|
||||
}
|
||||
|
||||
strategy_dict.update({
|
||||
'ai_analysis': ai_analysis,
|
||||
'onboarding_integration': onboarding_integration,
|
||||
'completion_percentage': getattr(strategy, 'completion_percentage', 0)
|
||||
})
|
||||
|
||||
enhanced_strategies.append(strategy_dict)
|
||||
|
||||
logger.info(f"✅ Retrieved {len(enhanced_strategies)} enhanced strategies")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Enhanced content strategies retrieved successfully",
|
||||
"strategies": enhanced_strategies,
|
||||
"total_count": len(enhanced_strategies),
|
||||
"user_id": user_id
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error retrieving enhanced strategies: {str(e)}")
|
||||
raise
|
||||
|
||||
async def _enhance_strategy_with_onboarding_data(self, strategy: EnhancedContentStrategy, user_id: int, db: Session) -> None:
|
||||
"""Enhance strategy with intelligent auto-population from onboarding data."""
|
||||
try:
|
||||
logger.info(f"Enhancing strategy with onboarding data for user: {user_id}")
|
||||
|
||||
# Get onboarding session
|
||||
onboarding_session = db.query(OnboardingSession).filter(
|
||||
OnboardingSession.user_id == user_id
|
||||
).first()
|
||||
|
||||
if not onboarding_session:
|
||||
logger.info("No onboarding session found for user")
|
||||
return
|
||||
|
||||
# Get website analysis data
|
||||
website_analysis = db.query(WebsiteAnalysis).filter(
|
||||
WebsiteAnalysis.session_id == onboarding_session.id
|
||||
).first()
|
||||
|
||||
# Get research preferences data
|
||||
research_preferences = db.query(ResearchPreferences).filter(
|
||||
ResearchPreferences.session_id == onboarding_session.id
|
||||
).first()
|
||||
|
||||
# Get API keys data
|
||||
api_keys = db.query(APIKey).filter(
|
||||
APIKey.session_id == onboarding_session.id
|
||||
).all()
|
||||
|
||||
# Auto-populate fields from onboarding data
|
||||
auto_populated_fields = {}
|
||||
data_sources = {}
|
||||
|
||||
if website_analysis:
|
||||
# Extract content preferences from writing style
|
||||
if website_analysis.writing_style:
|
||||
strategy.content_preferences = extract_content_preferences_from_style(
|
||||
website_analysis.writing_style
|
||||
)
|
||||
auto_populated_fields['content_preferences'] = 'website_analysis'
|
||||
|
||||
# Extract target audience from analysis
|
||||
if website_analysis.target_audience:
|
||||
strategy.target_audience = website_analysis.target_audience
|
||||
auto_populated_fields['target_audience'] = 'website_analysis'
|
||||
|
||||
# Extract brand voice from style guidelines
|
||||
if website_analysis.style_guidelines:
|
||||
strategy.brand_voice = extract_brand_voice_from_guidelines(
|
||||
website_analysis.style_guidelines
|
||||
)
|
||||
auto_populated_fields['brand_voice'] = 'website_analysis'
|
||||
|
||||
data_sources['website_analysis'] = website_analysis.to_dict()
|
||||
|
||||
if research_preferences:
|
||||
# Extract content types from research preferences
|
||||
if research_preferences.content_types:
|
||||
strategy.preferred_formats = research_preferences.content_types
|
||||
auto_populated_fields['preferred_formats'] = 'research_preferences'
|
||||
|
||||
# Extract writing style from preferences
|
||||
if research_preferences.writing_style:
|
||||
strategy.editorial_guidelines = extract_editorial_guidelines_from_style(
|
||||
research_preferences.writing_style
|
||||
)
|
||||
auto_populated_fields['editorial_guidelines'] = 'research_preferences'
|
||||
|
||||
data_sources['research_preferences'] = research_preferences.to_dict()
|
||||
|
||||
# Create onboarding data integration record
|
||||
integration = OnboardingDataIntegration(
|
||||
user_id=user_id,
|
||||
strategy_id=strategy.id,
|
||||
website_analysis_data=data_sources.get('website_analysis'),
|
||||
research_preferences_data=data_sources.get('research_preferences'),
|
||||
api_keys_data=[key.to_dict() for key in api_keys] if api_keys else None,
|
||||
auto_populated_fields=auto_populated_fields,
|
||||
field_mappings=create_field_mappings(),
|
||||
data_quality_scores=calculate_data_quality_scores(data_sources),
|
||||
confidence_levels={}, # Will be calculated by data quality service
|
||||
data_freshness={} # Will be calculated by data quality service
|
||||
)
|
||||
|
||||
db.add(integration)
|
||||
db.commit()
|
||||
|
||||
# Update strategy with onboarding data used
|
||||
strategy.onboarding_data_used = {
|
||||
'auto_populated_fields': auto_populated_fields,
|
||||
'data_sources': list(data_sources.keys()),
|
||||
'integration_id': integration.id
|
||||
}
|
||||
|
||||
logger.info(f"Strategy enhanced with onboarding data: {len(auto_populated_fields)} fields auto-populated")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error enhancing strategy with onboarding data: {str(e)}")
|
||||
# Don't raise error, just log it as this is enhancement, not core functionality
|
||||
|
||||
async def create_enhanced_strategy_legacy(self, strategy_data: Dict[str, Any], user_id: int, db: Session) -> EnhancedContentStrategy:
|
||||
"""Create enhanced content strategy with all integrations (legacy method for compatibility)."""
|
||||
try:
|
||||
logger.info(f"Creating enhanced strategy for user: {user_id}")
|
||||
|
||||
# Validate strategy data
|
||||
validation_result = self.validation_service.validate_strategy_data(strategy_data)
|
||||
if not validation_result['is_valid']:
|
||||
logger.error(f"Strategy validation failed: {validation_result['errors']}")
|
||||
raise ValueError(f"Invalid strategy data: {'; '.join(validation_result['errors'])}")
|
||||
|
||||
# Process onboarding data
|
||||
onboarding_data = await self._process_onboarding_data(user_id, db)
|
||||
|
||||
# Transform onboarding data to fields
|
||||
field_transformations = self.field_transformation_service.transform_onboarding_data_to_fields(onboarding_data)
|
||||
|
||||
# Merge strategy data with onboarding data
|
||||
enhanced_strategy_data = self._merge_strategy_with_onboarding(strategy_data, field_transformations)
|
||||
|
||||
# Create strategy object
|
||||
strategy = EnhancedContentStrategy(
|
||||
user_id=user_id,
|
||||
**enhanced_strategy_data,
|
||||
created_at=datetime.utcnow(),
|
||||
updated_at=datetime.utcnow()
|
||||
)
|
||||
|
||||
# Save to database
|
||||
db.add(strategy)
|
||||
db.commit()
|
||||
db.refresh(strategy)
|
||||
|
||||
# Generate AI recommendations
|
||||
await self.ai_recommendations_service.generate_comprehensive_recommendations(strategy, db)
|
||||
|
||||
# Cache strategy data
|
||||
await self.caching_service.cache_strategy(strategy.id, strategy.to_dict())
|
||||
|
||||
return strategy
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating enhanced strategy: {str(e)}")
|
||||
db.rollback()
|
||||
raise
|
||||
|
||||
async def get_enhanced_strategy(self, strategy_id: int, db: Session) -> Optional[EnhancedContentStrategy]:
|
||||
"""Get a single enhanced strategy by ID."""
|
||||
try:
|
||||
# Try cache first
|
||||
cached_strategy = await self.caching_service.get_cached_strategy(strategy_id)
|
||||
if cached_strategy:
|
||||
return cached_strategy
|
||||
|
||||
# Get from database
|
||||
strategy = db.query(EnhancedContentStrategy).filter(
|
||||
EnhancedContentStrategy.id == strategy_id
|
||||
).first()
|
||||
|
||||
if strategy:
|
||||
# Cache the strategy
|
||||
await self.caching_service.cache_strategy(strategy_id, strategy.to_dict())
|
||||
|
||||
return strategy
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting enhanced strategy: {str(e)}")
|
||||
raise
|
||||
|
||||
async def update_enhanced_strategy(self, strategy_id: int, update_data: Dict[str, Any], db: Session) -> Optional[EnhancedContentStrategy]:
|
||||
"""Update an enhanced strategy."""
|
||||
try:
|
||||
# Get existing strategy
|
||||
strategy = await self.get_enhanced_strategy(strategy_id, db)
|
||||
if not strategy:
|
||||
return None
|
||||
|
||||
# Validate update data
|
||||
validation_result = self.validation_service.validate_strategy_data(update_data)
|
||||
if not validation_result['is_valid']:
|
||||
logger.error(f"Update validation failed: {validation_result['errors']}")
|
||||
raise ValueError(f"Invalid update data: {'; '.join(validation_result['errors'])}")
|
||||
|
||||
# Update strategy fields
|
||||
for field, value in update_data.items():
|
||||
if hasattr(strategy, field):
|
||||
setattr(strategy, field, value)
|
||||
|
||||
strategy.updated_at = datetime.utcnow()
|
||||
|
||||
# Check if AI recommendations should be regenerated
|
||||
if self._should_regenerate_ai_recommendations(update_data):
|
||||
await self.strategy_analyzer.generate_comprehensive_ai_recommendations(strategy, db)
|
||||
|
||||
# Save to database
|
||||
db.commit()
|
||||
db.refresh(strategy)
|
||||
|
||||
# Update cache
|
||||
await self.caching_service.cache_strategy(strategy_id, strategy.to_dict())
|
||||
|
||||
return strategy
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating enhanced strategy: {str(e)}")
|
||||
db.rollback()
|
||||
raise
|
||||
|
||||
async def get_onboarding_data(self, user_id: int, db: Session) -> Dict[str, Any]:
|
||||
"""Get onboarding data for a user."""
|
||||
try:
|
||||
return await self.data_processor_service.get_onboarding_data(user_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting onboarding data: {str(e)}")
|
||||
raise
|
||||
|
||||
async def get_ai_analysis(self, strategy_id: int, analysis_type: str, db: Session) -> Optional[Dict[str, Any]]:
|
||||
"""Get AI analysis for a strategy."""
|
||||
try:
|
||||
return await self.strategy_analyzer.get_latest_ai_analysis(strategy_id, db)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting AI analysis: {str(e)}")
|
||||
raise
|
||||
|
||||
async def get_system_health(self, db: Session) -> Dict[str, Any]:
|
||||
"""Get system health status."""
|
||||
try:
|
||||
return await self.health_monitoring_service.get_system_health(db)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting system health: {str(e)}")
|
||||
raise
|
||||
|
||||
async def get_performance_report(self) -> Dict[str, Any]:
|
||||
"""Get performance report."""
|
||||
try:
|
||||
return await self.performance_optimization_service.get_performance_report()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting performance report: {str(e)}")
|
||||
raise
|
||||
|
||||
async def _process_onboarding_data(self, user_id: int, db: Session) -> Dict[str, Any]:
|
||||
"""Process onboarding data for strategy creation."""
|
||||
try:
|
||||
return await self.data_processor_service.get_onboarding_data(user_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing onboarding data: {str(e)}")
|
||||
raise
|
||||
|
||||
def _merge_strategy_with_onboarding(self, strategy_data: Dict[str, Any], field_transformations: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Merge strategy data with onboarding data."""
|
||||
merged_data = strategy_data.copy()
|
||||
|
||||
for field, transformation in field_transformations.items():
|
||||
if field not in merged_data or merged_data[field] is None:
|
||||
merged_data[field] = transformation.get('value')
|
||||
|
||||
return merged_data
|
||||
|
||||
def _should_regenerate_ai_recommendations(self, update_data: Dict[str, Any]) -> bool:
|
||||
"""Determine if AI recommendations should be regenerated based on updates."""
|
||||
critical_fields = [
|
||||
'business_objectives', 'target_metrics', 'industry',
|
||||
'content_preferences', 'target_audience', 'competitive_position'
|
||||
]
|
||||
|
||||
return any(field in update_data for field in critical_fields)
|
||||
|
||||
def get_strategic_input_fields(self) -> List[Dict[str, Any]]:
|
||||
"""Get strategic input fields configuration."""
|
||||
return STRATEGIC_INPUT_FIELDS
|
||||
|
||||
def get_service_constants(self) -> Dict[str, Any]:
|
||||
"""Get service constants."""
|
||||
return SERVICE_CONSTANTS
|
||||
|
||||
async def validate_strategy_data(self, strategy_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate strategy data."""
|
||||
try:
|
||||
return self.validation_service.validate_strategy_data(strategy_data)
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating strategy data: {str(e)}")
|
||||
raise
|
||||
|
||||
async def process_data_for_output(self, data: Dict[str, Any], output_format: str = 'json') -> Union[str, Dict[str, Any]]:
|
||||
"""Process data for specific output format."""
|
||||
try:
|
||||
if output_format == 'json':
|
||||
return data
|
||||
elif output_format == 'xml':
|
||||
# Convert to XML format
|
||||
return self._convert_to_xml(data)
|
||||
else:
|
||||
raise ValueError(f"Unsupported output format: {output_format}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing data for output: {str(e)}")
|
||||
raise
|
||||
|
||||
async def optimize_strategy_operation(self, operation_name: str, operation_func, *args, **kwargs) -> Dict[str, Any]:
|
||||
"""Optimize strategy operation with performance monitoring."""
|
||||
try:
|
||||
return await self.performance_optimization_service.optimize_operation(
|
||||
operation_name, operation_func, *args, **kwargs
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error optimizing strategy operation: {str(e)}")
|
||||
raise
|
||||
|
||||
def _convert_to_xml(self, data: Dict[str, Any]) -> str:
|
||||
"""Convert data to XML format (placeholder implementation)."""
|
||||
# This would be implemented with proper XML conversion
|
||||
return f"<strategy>{str(data)}</strategy>"
|
||||
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
Onboarding Module
|
||||
Onboarding data integration and processing.
|
||||
"""
|
||||
|
||||
from .data_integration import OnboardingDataIntegrationService
|
||||
from .data_quality import DataQualityService
|
||||
from .field_transformation import FieldTransformationService
|
||||
from .data_processor import OnboardingDataProcessor
|
||||
|
||||
__all__ = [
|
||||
'OnboardingDataIntegrationService',
|
||||
'DataQualityService',
|
||||
'FieldTransformationService',
|
||||
'OnboardingDataProcessor'
|
||||
]
|
||||
@@ -0,0 +1,409 @@
|
||||
"""
|
||||
Onboarding Data Integration Service
|
||||
Onboarding data integration and processing.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any, Optional, List
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy.orm import Session
|
||||
import traceback
|
||||
|
||||
# Import database models
|
||||
from models.enhanced_strategy_models import (
|
||||
OnboardingDataIntegration
|
||||
)
|
||||
from models.onboarding import (
|
||||
OnboardingSession,
|
||||
WebsiteAnalysis,
|
||||
ResearchPreferences,
|
||||
APIKey
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class OnboardingDataIntegrationService:
|
||||
"""Service for onboarding data integration and processing."""
|
||||
|
||||
def __init__(self):
|
||||
self.data_freshness_threshold = timedelta(hours=24)
|
||||
self.max_analysis_age = timedelta(days=7)
|
||||
|
||||
async def process_onboarding_data(self, user_id: int, db: Session) -> Dict[str, Any]:
|
||||
"""Process and integrate all onboarding data for a user."""
|
||||
try:
|
||||
logger.info(f"Processing onboarding data for user: {user_id}")
|
||||
|
||||
# Get all onboarding data sources
|
||||
website_analysis = self._get_website_analysis(user_id, db)
|
||||
research_preferences = self._get_research_preferences(user_id, db)
|
||||
api_keys_data = self._get_api_keys_data(user_id, db)
|
||||
onboarding_session = self._get_onboarding_session(user_id, db)
|
||||
|
||||
# Log data source status
|
||||
logger.info(f"Data source status for user {user_id}:")
|
||||
logger.info(f" - Website analysis: {'✅ Found' if website_analysis else '❌ Missing'}")
|
||||
logger.info(f" - Research preferences: {'✅ Found' if research_preferences else '❌ Missing'}")
|
||||
logger.info(f" - API keys data: {'✅ Found' if api_keys_data else '❌ Missing'}")
|
||||
logger.info(f" - Onboarding session: {'✅ Found' if onboarding_session else '❌ Missing'}")
|
||||
|
||||
# Process and integrate data
|
||||
integrated_data = {
|
||||
'website_analysis': website_analysis,
|
||||
'research_preferences': research_preferences,
|
||||
'api_keys_data': api_keys_data,
|
||||
'onboarding_session': onboarding_session,
|
||||
'data_quality': self._assess_data_quality(website_analysis, research_preferences, api_keys_data),
|
||||
'processing_timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
# Log data quality assessment
|
||||
data_quality = integrated_data['data_quality']
|
||||
logger.info(f"Data quality assessment for user {user_id}:")
|
||||
logger.info(f" - Completeness: {data_quality.get('completeness', 0):.2f}")
|
||||
logger.info(f" - Freshness: {data_quality.get('freshness', 0):.2f}")
|
||||
logger.info(f" - Relevance: {data_quality.get('relevance', 0):.2f}")
|
||||
logger.info(f" - Confidence: {data_quality.get('confidence', 0):.2f}")
|
||||
|
||||
# Store integrated data
|
||||
await self._store_integrated_data(user_id, integrated_data, db)
|
||||
|
||||
logger.info(f"Onboarding data processed successfully for user: {user_id}")
|
||||
return integrated_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing onboarding data for user {user_id}: {str(e)}")
|
||||
logger.error("Traceback:\n%s", traceback.format_exc())
|
||||
return self._get_fallback_data()
|
||||
|
||||
def _get_website_analysis(self, user_id: int, db: Session) -> Dict[str, Any]:
|
||||
"""Get website analysis data for the user."""
|
||||
try:
|
||||
# Get the latest onboarding session for the user
|
||||
session = db.query(OnboardingSession).filter(
|
||||
OnboardingSession.user_id == user_id
|
||||
).order_by(OnboardingSession.updated_at.desc()).first()
|
||||
|
||||
if not session:
|
||||
logger.warning(f"No onboarding session found for user {user_id}")
|
||||
return {}
|
||||
|
||||
# Get the latest website analysis for this session
|
||||
website_analysis = db.query(WebsiteAnalysis).filter(
|
||||
WebsiteAnalysis.session_id == session.id
|
||||
).order_by(WebsiteAnalysis.updated_at.desc()).first()
|
||||
|
||||
if not website_analysis:
|
||||
logger.warning(f"No website analysis found for user {user_id}")
|
||||
return {}
|
||||
|
||||
# Convert to dictionary and add metadata
|
||||
analysis_data = website_analysis.to_dict()
|
||||
analysis_data['data_freshness'] = self._calculate_freshness(website_analysis.updated_at)
|
||||
analysis_data['confidence_level'] = 0.9 if website_analysis.status == 'completed' else 0.5
|
||||
|
||||
logger.info(f"Retrieved website analysis for user {user_id}: {website_analysis.website_url}")
|
||||
return analysis_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting website analysis for user {user_id}: {str(e)}")
|
||||
return {}
|
||||
|
||||
def _get_research_preferences(self, user_id: int, db: Session) -> Dict[str, Any]:
|
||||
"""Get research preferences data for the user."""
|
||||
try:
|
||||
# Get the latest onboarding session for the user
|
||||
session = db.query(OnboardingSession).filter(
|
||||
OnboardingSession.user_id == user_id
|
||||
).order_by(OnboardingSession.updated_at.desc()).first()
|
||||
|
||||
if not session:
|
||||
logger.warning(f"No onboarding session found for user {user_id}")
|
||||
return {}
|
||||
|
||||
# Get research preferences for this session
|
||||
research_prefs = db.query(ResearchPreferences).filter(
|
||||
ResearchPreferences.session_id == session.id
|
||||
).first()
|
||||
|
||||
if not research_prefs:
|
||||
logger.warning(f"No research preferences found for user {user_id}")
|
||||
return {}
|
||||
|
||||
# Convert to dictionary and add metadata
|
||||
prefs_data = research_prefs.to_dict()
|
||||
prefs_data['data_freshness'] = self._calculate_freshness(research_prefs.updated_at)
|
||||
prefs_data['confidence_level'] = 0.9
|
||||
|
||||
logger.info(f"Retrieved research preferences for user {user_id}")
|
||||
return prefs_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting research preferences for user {user_id}: {str(e)}")
|
||||
return {}
|
||||
|
||||
def _get_api_keys_data(self, user_id: int, db: Session) -> Dict[str, Any]:
|
||||
"""Get API keys data for the user."""
|
||||
try:
|
||||
# Get the latest onboarding session for the user
|
||||
session = db.query(OnboardingSession).filter(
|
||||
OnboardingSession.user_id == user_id
|
||||
).order_by(OnboardingSession.updated_at.desc()).first()
|
||||
|
||||
if not session:
|
||||
logger.warning(f"No onboarding session found for user {user_id}")
|
||||
return {}
|
||||
|
||||
# Get all API keys for this session
|
||||
api_keys = db.query(APIKey).filter(
|
||||
APIKey.session_id == session.id
|
||||
).all()
|
||||
|
||||
if not api_keys:
|
||||
logger.warning(f"No API keys found for user {user_id}")
|
||||
return {}
|
||||
|
||||
# Convert to dictionary format
|
||||
api_data = {
|
||||
'api_keys': [key.to_dict() for key in api_keys],
|
||||
'total_keys': len(api_keys),
|
||||
'providers': [key.provider for key in api_keys],
|
||||
'data_freshness': self._calculate_freshness(session.updated_at),
|
||||
'confidence_level': 0.8
|
||||
}
|
||||
|
||||
logger.info(f"Retrieved {len(api_keys)} API keys for user {user_id}")
|
||||
return api_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting API keys data for user {user_id}: {str(e)}")
|
||||
return {}
|
||||
|
||||
def _get_onboarding_session(self, user_id: int, db: Session) -> Dict[str, Any]:
|
||||
"""Get onboarding session data for the user."""
|
||||
try:
|
||||
# Get the latest onboarding session for the user
|
||||
session = db.query(OnboardingSession).filter(
|
||||
OnboardingSession.user_id == user_id
|
||||
).order_by(OnboardingSession.updated_at.desc()).first()
|
||||
|
||||
if not session:
|
||||
logger.warning(f"No onboarding session found for user {user_id}")
|
||||
return {}
|
||||
|
||||
# Convert to dictionary
|
||||
session_data = {
|
||||
'id': session.id,
|
||||
'user_id': session.user_id,
|
||||
'current_step': session.current_step,
|
||||
'progress': session.progress,
|
||||
'started_at': session.started_at.isoformat() if session.started_at else None,
|
||||
'updated_at': session.updated_at.isoformat() if session.updated_at else None,
|
||||
'data_freshness': self._calculate_freshness(session.updated_at),
|
||||
'confidence_level': 0.9
|
||||
}
|
||||
|
||||
logger.info(f"Retrieved onboarding session for user {user_id}: step {session.current_step}, progress {session.progress}%")
|
||||
return session_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting onboarding session for user {user_id}: {str(e)}")
|
||||
return {}
|
||||
|
||||
def _assess_data_quality(self, website_analysis: Dict, research_preferences: Dict, api_keys_data: Dict) -> Dict[str, Any]:
|
||||
"""Assess the quality and completeness of onboarding data."""
|
||||
try:
|
||||
quality_metrics = {
|
||||
'overall_score': 0.0,
|
||||
'completeness': 0.0,
|
||||
'freshness': 0.0,
|
||||
'relevance': 0.0,
|
||||
'confidence': 0.0
|
||||
}
|
||||
|
||||
# Calculate completeness
|
||||
total_fields = 0
|
||||
filled_fields = 0
|
||||
|
||||
# Website analysis completeness
|
||||
website_fields = ['domain', 'industry', 'business_type', 'target_audience', 'content_goals']
|
||||
for field in website_fields:
|
||||
total_fields += 1
|
||||
if website_analysis.get(field):
|
||||
filled_fields += 1
|
||||
|
||||
# Research preferences completeness
|
||||
research_fields = ['research_topics', 'content_types', 'target_audience', 'industry_focus']
|
||||
for field in research_fields:
|
||||
total_fields += 1
|
||||
if research_preferences.get(field):
|
||||
filled_fields += 1
|
||||
|
||||
# API keys completeness
|
||||
total_fields += 1
|
||||
if api_keys_data:
|
||||
filled_fields += 1
|
||||
|
||||
quality_metrics['completeness'] = filled_fields / total_fields if total_fields > 0 else 0.0
|
||||
|
||||
# Calculate freshness
|
||||
freshness_scores = []
|
||||
for data_source in [website_analysis, research_preferences]:
|
||||
if data_source.get('data_freshness'):
|
||||
freshness_scores.append(data_source['data_freshness'])
|
||||
|
||||
quality_metrics['freshness'] = sum(freshness_scores) / len(freshness_scores) if freshness_scores else 0.0
|
||||
|
||||
# Calculate relevance (based on data presence and quality)
|
||||
relevance_score = 0.0
|
||||
if website_analysis.get('domain'):
|
||||
relevance_score += 0.4
|
||||
if research_preferences.get('research_topics'):
|
||||
relevance_score += 0.3
|
||||
if api_keys_data:
|
||||
relevance_score += 0.3
|
||||
|
||||
quality_metrics['relevance'] = relevance_score
|
||||
|
||||
# Calculate confidence
|
||||
quality_metrics['confidence'] = (quality_metrics['completeness'] + quality_metrics['freshness'] + quality_metrics['relevance']) / 3
|
||||
|
||||
# Calculate overall score
|
||||
quality_metrics['overall_score'] = quality_metrics['confidence']
|
||||
|
||||
return quality_metrics
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error assessing data quality: {str(e)}")
|
||||
return {
|
||||
'overall_score': 0.0,
|
||||
'completeness': 0.0,
|
||||
'freshness': 0.0,
|
||||
'relevance': 0.0,
|
||||
'confidence': 0.0
|
||||
}
|
||||
|
||||
def _calculate_freshness(self, created_at: datetime) -> float:
|
||||
"""Calculate data freshness score (0.0 to 1.0)."""
|
||||
try:
|
||||
age = datetime.utcnow() - created_at
|
||||
|
||||
if age <= self.data_freshness_threshold:
|
||||
return 1.0
|
||||
elif age <= self.max_analysis_age:
|
||||
# Linear decay from 1.0 to 0.5
|
||||
decay_factor = 1.0 - (age - self.data_freshness_threshold) / (self.max_analysis_age - self.data_freshness_threshold) * 0.5
|
||||
return max(0.5, decay_factor)
|
||||
else:
|
||||
return 0.5 # Minimum freshness for old data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating data freshness: {str(e)}")
|
||||
return 0.5
|
||||
|
||||
def _check_api_data_availability(self, api_key_data: Dict) -> bool:
|
||||
"""Check if API key has available data."""
|
||||
try:
|
||||
# Check if API key has been used recently and has data
|
||||
if api_key_data.get('last_used') and api_key_data.get('usage_count', 0) > 0:
|
||||
return api_key_data.get('data_available', False)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking API data availability: {str(e)}")
|
||||
return False
|
||||
|
||||
async def _store_integrated_data(self, user_id: int, integrated_data: Dict[str, Any], db: Session) -> None:
|
||||
"""Store integrated onboarding data."""
|
||||
try:
|
||||
# Create or update integrated data record
|
||||
existing_record = db.query(OnboardingDataIntegration).filter(
|
||||
OnboardingDataIntegration.user_id == user_id
|
||||
).first()
|
||||
|
||||
if existing_record:
|
||||
# Use legacy columns that are known to exist
|
||||
if hasattr(existing_record, 'website_analysis_data'):
|
||||
existing_record.website_analysis_data = integrated_data.get('website_analysis', {})
|
||||
if hasattr(existing_record, 'research_preferences_data'):
|
||||
existing_record.research_preferences_data = integrated_data.get('research_preferences', {})
|
||||
if hasattr(existing_record, 'api_keys_data'):
|
||||
existing_record.api_keys_data = integrated_data.get('api_keys_data', {})
|
||||
existing_record.updated_at = datetime.utcnow()
|
||||
else:
|
||||
new_kwargs = {
|
||||
'user_id': user_id,
|
||||
'created_at': datetime.utcnow(),
|
||||
'updated_at': datetime.utcnow()
|
||||
}
|
||||
if 'website_analysis' in integrated_data:
|
||||
new_kwargs['website_analysis_data'] = integrated_data.get('website_analysis', {})
|
||||
if 'research_preferences' in integrated_data:
|
||||
new_kwargs['research_preferences_data'] = integrated_data.get('research_preferences', {})
|
||||
if 'api_keys_data' in integrated_data:
|
||||
new_kwargs['api_keys_data'] = integrated_data.get('api_keys_data', {})
|
||||
|
||||
new_record = OnboardingDataIntegration(**new_kwargs)
|
||||
db.add(new_record)
|
||||
|
||||
db.commit()
|
||||
logger.info(f"Integrated onboarding data stored for user: {user_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error storing integrated data for user {user_id}: {str(e)}")
|
||||
db.rollback()
|
||||
# Soft-fail storage: do not break the refresh path
|
||||
return
|
||||
|
||||
def _get_fallback_data(self) -> Dict[str, Any]:
|
||||
"""Get fallback data when processing fails."""
|
||||
return {
|
||||
'website_analysis': {},
|
||||
'research_preferences': {},
|
||||
'api_keys_data': {},
|
||||
'onboarding_session': {},
|
||||
'data_quality': {
|
||||
'overall_score': 0.0,
|
||||
'completeness': 0.0,
|
||||
'freshness': 0.0,
|
||||
'relevance': 0.0,
|
||||
'confidence': 0.0
|
||||
},
|
||||
'processing_timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
async def get_integrated_data(self, user_id: int, db: Session) -> Optional[Dict[str, Any]]:
|
||||
"""Get previously integrated onboarding data for a user."""
|
||||
try:
|
||||
record = db.query(OnboardingDataIntegration).filter(
|
||||
OnboardingDataIntegration.user_id == user_id
|
||||
).first()
|
||||
|
||||
if record:
|
||||
# Reconstruct integrated data from stored fields
|
||||
integrated_data = {
|
||||
'website_analysis': record.website_analysis_data or {},
|
||||
'research_preferences': record.research_preferences_data or {},
|
||||
'api_keys_data': record.api_keys_data or {},
|
||||
'onboarding_session': {},
|
||||
'data_quality': self._assess_data_quality(
|
||||
record.website_analysis_data or {},
|
||||
record.research_preferences_data or {},
|
||||
record.api_keys_data or {}
|
||||
),
|
||||
'processing_timestamp': record.updated_at.isoformat()
|
||||
}
|
||||
|
||||
# Check if data is still fresh
|
||||
updated_at = record.updated_at
|
||||
if datetime.utcnow() - updated_at <= self.data_freshness_threshold:
|
||||
return integrated_data
|
||||
else:
|
||||
logger.info(f"Integrated data is stale for user {user_id}, reprocessing...")
|
||||
return await self.process_onboarding_data(user_id, db)
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting integrated data for user {user_id}: {str(e)}")
|
||||
return None
|
||||
@@ -0,0 +1,301 @@
|
||||
"""
|
||||
Onboarding Data Processor
|
||||
Handles processing and transformation of onboarding data for strategic intelligence.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional, Union
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
# Import database models
|
||||
from models.onboarding import OnboardingSession, WebsiteAnalysis, ResearchPreferences, APIKey
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class OnboardingDataProcessor:
|
||||
"""Processes and transforms onboarding data for strategic intelligence generation."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def process_onboarding_data(self, user_id: int, db: Session) -> Optional[Dict[str, Any]]:
|
||||
"""Process onboarding data for a user and return structured data for strategic intelligence."""
|
||||
try:
|
||||
logger.info(f"Processing onboarding data for user {user_id}")
|
||||
|
||||
# Get onboarding session
|
||||
onboarding_session = db.query(OnboardingSession).filter(
|
||||
OnboardingSession.user_id == user_id
|
||||
).first()
|
||||
|
||||
if not onboarding_session:
|
||||
logger.warning(f"No onboarding session found for user {user_id}")
|
||||
return None
|
||||
|
||||
# Get website analysis data
|
||||
website_analysis = db.query(WebsiteAnalysis).filter(
|
||||
WebsiteAnalysis.session_id == onboarding_session.id
|
||||
).first()
|
||||
|
||||
# Get research preferences data
|
||||
research_preferences = db.query(ResearchPreferences).filter(
|
||||
ResearchPreferences.session_id == onboarding_session.id
|
||||
).first()
|
||||
|
||||
# Get API keys data
|
||||
api_keys = db.query(APIKey).filter(
|
||||
APIKey.session_id == onboarding_session.id
|
||||
).all()
|
||||
|
||||
# Process each data type
|
||||
processed_data = {
|
||||
'website_analysis': await self._process_website_analysis(website_analysis),
|
||||
'research_preferences': await self._process_research_preferences(research_preferences),
|
||||
'api_keys_data': await self._process_api_keys_data(api_keys),
|
||||
'session_data': self._process_session_data(onboarding_session)
|
||||
}
|
||||
|
||||
# Transform into strategic intelligence format
|
||||
strategic_data = self._transform_to_strategic_format(processed_data)
|
||||
|
||||
logger.info(f"Successfully processed onboarding data for user {user_id}")
|
||||
return strategic_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing onboarding data for user {user_id}: {str(e)}")
|
||||
return None
|
||||
|
||||
async def _process_website_analysis(self, website_analysis: Optional[WebsiteAnalysis]) -> Dict[str, Any]:
|
||||
"""Process website analysis data."""
|
||||
if not website_analysis:
|
||||
return {}
|
||||
|
||||
try:
|
||||
return {
|
||||
'website_url': getattr(website_analysis, 'website_url', ''),
|
||||
'industry': getattr(website_analysis, 'industry', 'Technology'), # Default value if attribute doesn't exist
|
||||
'content_goals': getattr(website_analysis, 'content_goals', []),
|
||||
'performance_metrics': getattr(website_analysis, 'performance_metrics', {}),
|
||||
'traffic_sources': getattr(website_analysis, 'traffic_sources', []),
|
||||
'content_gaps': getattr(website_analysis, 'content_gaps', []),
|
||||
'topics': getattr(website_analysis, 'topics', []),
|
||||
'content_quality_score': getattr(website_analysis, 'content_quality_score', 0),
|
||||
'seo_opportunities': getattr(website_analysis, 'seo_opportunities', []),
|
||||
'competitors': getattr(website_analysis, 'competitors', []),
|
||||
'competitive_advantages': getattr(website_analysis, 'competitive_advantages', []),
|
||||
'market_gaps': getattr(website_analysis, 'market_gaps', []),
|
||||
'last_updated': website_analysis.updated_at.isoformat() if hasattr(website_analysis, 'updated_at') and website_analysis.updated_at else None
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing website analysis: {str(e)}")
|
||||
return {}
|
||||
|
||||
async def _process_research_preferences(self, research_preferences: Optional[ResearchPreferences]) -> Dict[str, Any]:
|
||||
"""Process research preferences data."""
|
||||
if not research_preferences:
|
||||
return {}
|
||||
|
||||
try:
|
||||
return {
|
||||
'content_preferences': {
|
||||
'preferred_formats': research_preferences.content_types,
|
||||
'content_topics': research_preferences.research_topics,
|
||||
'content_style': research_preferences.writing_style.get('tone', []) if research_preferences.writing_style else [],
|
||||
'content_length': research_preferences.content_length,
|
||||
'visual_preferences': research_preferences.visual_preferences
|
||||
},
|
||||
'audience_research': {
|
||||
'target_audience': research_preferences.target_audience.get('demographics', []) if research_preferences.target_audience else [],
|
||||
'audience_pain_points': research_preferences.target_audience.get('pain_points', []) if research_preferences.target_audience else [],
|
||||
'buying_journey': research_preferences.target_audience.get('buying_journey', {}) if research_preferences.target_audience else {},
|
||||
'consumption_patterns': research_preferences.target_audience.get('consumption_patterns', {}) if research_preferences.target_audience else {}
|
||||
},
|
||||
'research_goals': {
|
||||
'primary_goals': research_preferences.research_topics,
|
||||
'secondary_goals': research_preferences.content_types,
|
||||
'success_metrics': research_preferences.success_metrics
|
||||
},
|
||||
'last_updated': research_preferences.updated_at.isoformat() if research_preferences.updated_at else None
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing research preferences: {str(e)}")
|
||||
return {}
|
||||
|
||||
async def _process_api_keys_data(self, api_keys: List[APIKey]) -> Dict[str, Any]:
|
||||
"""Process API keys data."""
|
||||
try:
|
||||
processed_data = {
|
||||
'analytics_data': {},
|
||||
'social_media_data': {},
|
||||
'competitor_data': {},
|
||||
'last_updated': None
|
||||
}
|
||||
|
||||
for api_key in api_keys:
|
||||
if api_key.provider == 'google_analytics':
|
||||
processed_data['analytics_data']['google_analytics'] = {
|
||||
'connected': True,
|
||||
'data_available': True,
|
||||
'metrics': api_key.metrics if api_key.metrics else {}
|
||||
}
|
||||
elif api_key.provider == 'google_search_console':
|
||||
processed_data['analytics_data']['google_search_console'] = {
|
||||
'connected': True,
|
||||
'data_available': True,
|
||||
'metrics': api_key.metrics if api_key.metrics else {}
|
||||
}
|
||||
elif api_key.provider in ['linkedin', 'twitter', 'facebook']:
|
||||
processed_data['social_media_data'][api_key.provider] = {
|
||||
'connected': True,
|
||||
'followers': api_key.metrics.get('followers', 0) if api_key.metrics else 0
|
||||
}
|
||||
elif api_key.provider in ['semrush', 'ahrefs', 'moz']:
|
||||
processed_data['competitor_data'][api_key.provider] = {
|
||||
'connected': True,
|
||||
'competitors_analyzed': api_key.metrics.get('competitors_analyzed', 0) if api_key.metrics else 0
|
||||
}
|
||||
|
||||
# Update last_updated if this key is more recent
|
||||
if api_key.updated_at and (not processed_data['last_updated'] or api_key.updated_at > datetime.fromisoformat(processed_data['last_updated'])):
|
||||
processed_data['last_updated'] = api_key.updated_at.isoformat()
|
||||
|
||||
return processed_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing API keys data: {str(e)}")
|
||||
return {}
|
||||
|
||||
def _process_session_data(self, onboarding_session: OnboardingSession) -> Dict[str, Any]:
|
||||
"""Process onboarding session data."""
|
||||
try:
|
||||
return {
|
||||
'session_id': getattr(onboarding_session, 'id', None),
|
||||
'user_id': getattr(onboarding_session, 'user_id', None),
|
||||
'created_at': onboarding_session.created_at.isoformat() if hasattr(onboarding_session, 'created_at') and onboarding_session.created_at else None,
|
||||
'updated_at': onboarding_session.updated_at.isoformat() if hasattr(onboarding_session, 'updated_at') and onboarding_session.updated_at else None,
|
||||
'completion_status': getattr(onboarding_session, 'completion_status', 'in_progress'),
|
||||
'session_data': getattr(onboarding_session, 'session_data', {}),
|
||||
'progress_percentage': getattr(onboarding_session, 'progress_percentage', 0),
|
||||
'last_activity': getattr(onboarding_session, 'last_activity', None)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing session data: {str(e)}")
|
||||
return {}
|
||||
|
||||
def _transform_to_strategic_format(self, processed_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Transform processed onboarding data into strategic intelligence format."""
|
||||
try:
|
||||
website_data = processed_data.get('website_analysis', {})
|
||||
research_data = processed_data.get('research_preferences', {})
|
||||
api_data = processed_data.get('api_keys_data', {})
|
||||
session_data = processed_data.get('session_data', {})
|
||||
|
||||
# Return data in nested format that field transformation service expects
|
||||
return {
|
||||
'website_analysis': {
|
||||
'content_goals': website_data.get('content_goals', []),
|
||||
'performance_metrics': website_data.get('performance_metrics', {}),
|
||||
'competitors': website_data.get('competitors', []),
|
||||
'content_gaps': website_data.get('content_gaps', []),
|
||||
'industry': website_data.get('industry', 'Technology'),
|
||||
'target_audience': website_data.get('target_audience', {}),
|
||||
'business_type': website_data.get('business_type', 'Technology')
|
||||
},
|
||||
'research_preferences': {
|
||||
'content_types': research_data.get('content_preferences', {}).get('preferred_formats', []),
|
||||
'research_topics': research_data.get('research_topics', []),
|
||||
'performance_tracking': research_data.get('performance_tracking', []),
|
||||
'competitor_analysis': research_data.get('competitor_analysis', []),
|
||||
'target_audience': research_data.get('audience_research', {}).get('target_audience', {}),
|
||||
'industry_focus': research_data.get('industry_focus', []),
|
||||
'trend_analysis': research_data.get('trend_analysis', []),
|
||||
'content_calendar': research_data.get('content_calendar', {})
|
||||
},
|
||||
'onboarding_session': {
|
||||
'session_data': {
|
||||
'budget': session_data.get('budget', 3000),
|
||||
'team_size': session_data.get('team_size', 2),
|
||||
'timeline': session_data.get('timeline', '3 months'),
|
||||
'brand_voice': session_data.get('brand_voice', 'Professional yet approachable')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error transforming to strategic format: {str(e)}")
|
||||
return {}
|
||||
|
||||
def calculate_data_quality_scores(self, processed_data: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""Calculate quality scores for each data source."""
|
||||
scores = {}
|
||||
|
||||
for source, data in processed_data.items():
|
||||
if data and isinstance(data, dict):
|
||||
# Simple scoring based on data completeness
|
||||
total_fields = len(data)
|
||||
present_fields = len([v for v in data.values() if v is not None and v != {}])
|
||||
completeness = present_fields / total_fields if total_fields > 0 else 0.0
|
||||
scores[source] = completeness * 100
|
||||
else:
|
||||
scores[source] = 0.0
|
||||
|
||||
return scores
|
||||
|
||||
def calculate_confidence_levels(self, processed_data: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""Calculate confidence levels for processed data."""
|
||||
confidence_levels = {}
|
||||
|
||||
# Base confidence on data source quality
|
||||
base_confidence = {
|
||||
'website_analysis': 0.8,
|
||||
'research_preferences': 0.7,
|
||||
'api_keys_data': 0.6,
|
||||
'session_data': 0.9
|
||||
}
|
||||
|
||||
for source, data in processed_data.items():
|
||||
if data and isinstance(data, dict):
|
||||
# Adjust confidence based on data completeness
|
||||
quality_score = self.calculate_data_quality_scores({source: data})[source] / 100
|
||||
base_conf = base_confidence.get(source, 0.5)
|
||||
confidence_levels[source] = base_conf * quality_score
|
||||
else:
|
||||
confidence_levels[source] = 0.0
|
||||
|
||||
return confidence_levels
|
||||
|
||||
def calculate_data_freshness(self, session_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Calculate data freshness for onboarding data."""
|
||||
try:
|
||||
updated_at = session_data.get('updated_at')
|
||||
if not updated_at:
|
||||
return {'status': 'unknown', 'age_days': 'unknown'}
|
||||
|
||||
# Convert string to datetime if needed
|
||||
if isinstance(updated_at, str):
|
||||
try:
|
||||
updated_at = datetime.fromisoformat(updated_at.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
return {'status': 'unknown', 'age_days': 'unknown'}
|
||||
|
||||
age_days = (datetime.utcnow() - updated_at).days
|
||||
|
||||
if age_days <= 7:
|
||||
status = 'fresh'
|
||||
elif age_days <= 30:
|
||||
status = 'recent'
|
||||
elif age_days <= 90:
|
||||
status = 'aging'
|
||||
else:
|
||||
status = 'stale'
|
||||
|
||||
return {
|
||||
'status': status,
|
||||
'age_days': age_days,
|
||||
'last_updated': updated_at.isoformat() if hasattr(updated_at, 'isoformat') else str(updated_at)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating data freshness: {str(e)}")
|
||||
return {'status': 'unknown', 'age_days': 'unknown'}
|
||||
@@ -0,0 +1,532 @@
|
||||
"""
|
||||
Data Quality Service
|
||||
Onboarding data quality assessment.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DataQualityService:
|
||||
"""Service for assessing data quality and validation."""
|
||||
|
||||
def __init__(self):
|
||||
self.quality_thresholds = {
|
||||
'excellent': 0.9,
|
||||
'good': 0.7,
|
||||
'fair': 0.5,
|
||||
'poor': 0.3
|
||||
}
|
||||
|
||||
self.data_freshness_threshold = timedelta(hours=24)
|
||||
self.max_data_age = timedelta(days=30)
|
||||
|
||||
def assess_onboarding_data_quality(self, integrated_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Assess the overall quality of onboarding data."""
|
||||
try:
|
||||
logger.info("Assessing onboarding data quality")
|
||||
|
||||
quality_assessment = {
|
||||
'overall_score': 0.0,
|
||||
'completeness': 0.0,
|
||||
'freshness': 0.0,
|
||||
'accuracy': 0.0,
|
||||
'relevance': 0.0,
|
||||
'consistency': 0.0,
|
||||
'confidence': 0.0,
|
||||
'quality_level': 'poor',
|
||||
'recommendations': [],
|
||||
'issues': [],
|
||||
'assessment_timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
# Assess each data source
|
||||
website_quality = self._assess_website_analysis_quality(integrated_data.get('website_analysis', {}))
|
||||
research_quality = self._assess_research_preferences_quality(integrated_data.get('research_preferences', {}))
|
||||
api_quality = self._assess_api_keys_quality(integrated_data.get('api_keys_data', {}))
|
||||
session_quality = self._assess_onboarding_session_quality(integrated_data.get('onboarding_session', {}))
|
||||
|
||||
# Calculate overall quality metrics
|
||||
quality_assessment['completeness'] = self._calculate_completeness_score(
|
||||
website_quality, research_quality, api_quality, session_quality
|
||||
)
|
||||
|
||||
quality_assessment['freshness'] = self._calculate_freshness_score(
|
||||
website_quality, research_quality, api_quality, session_quality
|
||||
)
|
||||
|
||||
quality_assessment['accuracy'] = self._calculate_accuracy_score(
|
||||
website_quality, research_quality, api_quality, session_quality
|
||||
)
|
||||
|
||||
quality_assessment['relevance'] = self._calculate_relevance_score(
|
||||
website_quality, research_quality, api_quality, session_quality
|
||||
)
|
||||
|
||||
quality_assessment['consistency'] = self._calculate_consistency_score(
|
||||
website_quality, research_quality, api_quality, session_quality
|
||||
)
|
||||
|
||||
# Calculate confidence and overall score
|
||||
quality_assessment['confidence'] = (
|
||||
quality_assessment['completeness'] +
|
||||
quality_assessment['freshness'] +
|
||||
quality_assessment['accuracy'] +
|
||||
quality_assessment['relevance'] +
|
||||
quality_assessment['consistency']
|
||||
) / 5
|
||||
|
||||
quality_assessment['overall_score'] = quality_assessment['confidence']
|
||||
|
||||
# Determine quality level
|
||||
quality_assessment['quality_level'] = self._determine_quality_level(quality_assessment['overall_score'])
|
||||
|
||||
# Generate recommendations and identify issues
|
||||
quality_assessment['recommendations'] = self._generate_quality_recommendations(quality_assessment)
|
||||
quality_assessment['issues'] = self._identify_quality_issues(quality_assessment)
|
||||
|
||||
logger.info(f"Data quality assessment completed. Overall score: {quality_assessment['overall_score']:.2f}")
|
||||
return quality_assessment
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error assessing data quality: {str(e)}")
|
||||
# Raise exception instead of returning fallback data
|
||||
raise Exception(f"Failed to assess data quality: {str(e)}")
|
||||
|
||||
def _assess_website_analysis_quality(self, website_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Assess quality of website analysis data."""
|
||||
try:
|
||||
quality_metrics = {
|
||||
'completeness': 0.0,
|
||||
'freshness': 0.0,
|
||||
'accuracy': 0.0,
|
||||
'relevance': 0.0,
|
||||
'consistency': 0.0
|
||||
}
|
||||
|
||||
if not website_data:
|
||||
return quality_metrics
|
||||
|
||||
# Completeness assessment
|
||||
required_fields = ['domain', 'industry', 'business_type', 'target_audience', 'content_goals']
|
||||
present_fields = sum(1 for field in required_fields if website_data.get(field))
|
||||
quality_metrics['completeness'] = present_fields / len(required_fields)
|
||||
|
||||
# Freshness assessment
|
||||
if website_data.get('created_at'):
|
||||
try:
|
||||
created_at = datetime.fromisoformat(website_data['created_at'].replace('Z', '+00:00'))
|
||||
age = datetime.utcnow() - created_at
|
||||
quality_metrics['freshness'] = self._calculate_freshness_score_from_age(age)
|
||||
except Exception:
|
||||
quality_metrics['freshness'] = 0.5
|
||||
|
||||
# Accuracy assessment (based on data presence and format)
|
||||
accuracy_score = 0.0
|
||||
if website_data.get('domain') and isinstance(website_data['domain'], str):
|
||||
accuracy_score += 0.2
|
||||
if website_data.get('industry') and isinstance(website_data['industry'], str):
|
||||
accuracy_score += 0.2
|
||||
if website_data.get('business_type') and isinstance(website_data['business_type'], str):
|
||||
accuracy_score += 0.2
|
||||
if website_data.get('target_audience') and isinstance(website_data['target_audience'], str):
|
||||
accuracy_score += 0.2
|
||||
if website_data.get('content_goals') and isinstance(website_data['content_goals'], (str, list)):
|
||||
accuracy_score += 0.2
|
||||
quality_metrics['accuracy'] = accuracy_score
|
||||
|
||||
# Relevance assessment
|
||||
relevance_score = 0.0
|
||||
if website_data.get('domain'):
|
||||
relevance_score += 0.3
|
||||
if website_data.get('industry'):
|
||||
relevance_score += 0.3
|
||||
if website_data.get('content_goals'):
|
||||
relevance_score += 0.4
|
||||
quality_metrics['relevance'] = relevance_score
|
||||
|
||||
# Consistency assessment
|
||||
consistency_score = 0.0
|
||||
if website_data.get('domain') and website_data.get('industry'):
|
||||
consistency_score += 0.5
|
||||
if website_data.get('target_audience') and website_data.get('content_goals'):
|
||||
consistency_score += 0.5
|
||||
quality_metrics['consistency'] = consistency_score
|
||||
|
||||
return quality_metrics
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error assessing website analysis quality: {str(e)}")
|
||||
return {'completeness': 0.0, 'freshness': 0.0, 'accuracy': 0.0, 'relevance': 0.0, 'consistency': 0.0}
|
||||
|
||||
def _assess_research_preferences_quality(self, research_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Assess quality of research preferences data."""
|
||||
try:
|
||||
quality_metrics = {
|
||||
'completeness': 0.0,
|
||||
'freshness': 0.0,
|
||||
'accuracy': 0.0,
|
||||
'relevance': 0.0,
|
||||
'consistency': 0.0
|
||||
}
|
||||
|
||||
if not research_data:
|
||||
return quality_metrics
|
||||
|
||||
# Completeness assessment
|
||||
required_fields = ['research_topics', 'content_types', 'target_audience', 'industry_focus']
|
||||
present_fields = sum(1 for field in required_fields if research_data.get(field))
|
||||
quality_metrics['completeness'] = present_fields / len(required_fields)
|
||||
|
||||
# Freshness assessment
|
||||
if research_data.get('created_at'):
|
||||
try:
|
||||
created_at = datetime.fromisoformat(research_data['created_at'].replace('Z', '+00:00'))
|
||||
age = datetime.utcnow() - created_at
|
||||
quality_metrics['freshness'] = self._calculate_freshness_score_from_age(age)
|
||||
except Exception:
|
||||
quality_metrics['freshness'] = 0.5
|
||||
|
||||
# Accuracy assessment
|
||||
accuracy_score = 0.0
|
||||
if research_data.get('research_topics') and isinstance(research_data['research_topics'], (str, list)):
|
||||
accuracy_score += 0.25
|
||||
if research_data.get('content_types') and isinstance(research_data['content_types'], (str, list)):
|
||||
accuracy_score += 0.25
|
||||
if research_data.get('target_audience') and isinstance(research_data['target_audience'], str):
|
||||
accuracy_score += 0.25
|
||||
if research_data.get('industry_focus') and isinstance(research_data['industry_focus'], str):
|
||||
accuracy_score += 0.25
|
||||
quality_metrics['accuracy'] = accuracy_score
|
||||
|
||||
# Relevance assessment
|
||||
relevance_score = 0.0
|
||||
if research_data.get('research_topics'):
|
||||
relevance_score += 0.4
|
||||
if research_data.get('content_types'):
|
||||
relevance_score += 0.3
|
||||
if research_data.get('target_audience'):
|
||||
relevance_score += 0.3
|
||||
quality_metrics['relevance'] = relevance_score
|
||||
|
||||
# Consistency assessment
|
||||
consistency_score = 0.0
|
||||
if research_data.get('research_topics') and research_data.get('content_types'):
|
||||
consistency_score += 0.5
|
||||
if research_data.get('target_audience') and research_data.get('industry_focus'):
|
||||
consistency_score += 0.5
|
||||
quality_metrics['consistency'] = consistency_score
|
||||
|
||||
return quality_metrics
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error assessing research preferences quality: {str(e)}")
|
||||
return {'completeness': 0.0, 'freshness': 0.0, 'accuracy': 0.0, 'relevance': 0.0, 'consistency': 0.0}
|
||||
|
||||
def _assess_api_keys_quality(self, api_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Assess quality of API keys data."""
|
||||
try:
|
||||
quality_metrics = {
|
||||
'completeness': 0.0,
|
||||
'freshness': 0.0,
|
||||
'accuracy': 0.0,
|
||||
'relevance': 0.0,
|
||||
'consistency': 0.0
|
||||
}
|
||||
|
||||
if not api_data:
|
||||
return quality_metrics
|
||||
|
||||
# Completeness assessment
|
||||
total_apis = len(api_data)
|
||||
active_apis = sum(1 for api_info in api_data.values() if api_info.get('is_active'))
|
||||
quality_metrics['completeness'] = active_apis / max(total_apis, 1)
|
||||
|
||||
# Freshness assessment
|
||||
freshness_scores = []
|
||||
for api_info in api_data.values():
|
||||
if api_info.get('last_used'):
|
||||
try:
|
||||
last_used = datetime.fromisoformat(api_info['last_used'].replace('Z', '+00:00'))
|
||||
age = datetime.utcnow() - last_used
|
||||
freshness_scores.append(self._calculate_freshness_score_from_age(age))
|
||||
except Exception:
|
||||
freshness_scores.append(0.5)
|
||||
|
||||
quality_metrics['freshness'] = sum(freshness_scores) / len(freshness_scores) if freshness_scores else 0.5
|
||||
|
||||
# Accuracy assessment
|
||||
accuracy_score = 0.0
|
||||
for api_info in api_data.values():
|
||||
if api_info.get('service_name') and api_info.get('is_active'):
|
||||
accuracy_score += 0.5
|
||||
if api_info.get('data_available'):
|
||||
accuracy_score += 0.5
|
||||
quality_metrics['accuracy'] = accuracy_score / max(len(api_data), 1)
|
||||
|
||||
# Relevance assessment
|
||||
relevant_apis = ['google_analytics', 'google_search_console', 'semrush', 'ahrefs', 'moz']
|
||||
relevant_count = sum(1 for api_name in api_data.keys() if api_name.lower() in relevant_apis)
|
||||
quality_metrics['relevance'] = relevant_count / max(len(api_data), 1)
|
||||
|
||||
# Consistency assessment
|
||||
consistency_score = 0.0
|
||||
if len(api_data) > 0:
|
||||
consistency_score = 0.5 # Basic consistency if APIs exist
|
||||
if any(api_info.get('data_available') for api_info in api_data.values()):
|
||||
consistency_score += 0.5
|
||||
quality_metrics['consistency'] = consistency_score
|
||||
|
||||
return quality_metrics
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error assessing API keys quality: {str(e)}")
|
||||
return {'completeness': 0.0, 'freshness': 0.0, 'accuracy': 0.0, 'relevance': 0.0, 'consistency': 0.0}
|
||||
|
||||
def _assess_onboarding_session_quality(self, session_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Assess quality of onboarding session data."""
|
||||
try:
|
||||
quality_metrics = {
|
||||
'completeness': 0.0,
|
||||
'freshness': 0.0,
|
||||
'accuracy': 0.0,
|
||||
'relevance': 0.0,
|
||||
'consistency': 0.0
|
||||
}
|
||||
|
||||
if not session_data:
|
||||
return quality_metrics
|
||||
|
||||
# Completeness assessment
|
||||
required_fields = ['session_id', 'completion_percentage', 'completed_steps', 'current_step']
|
||||
present_fields = sum(1 for field in required_fields if session_data.get(field))
|
||||
quality_metrics['completeness'] = present_fields / len(required_fields)
|
||||
|
||||
# Freshness assessment
|
||||
if session_data.get('updated_at'):
|
||||
try:
|
||||
updated_at = datetime.fromisoformat(session_data['updated_at'].replace('Z', '+00:00'))
|
||||
age = datetime.utcnow() - updated_at
|
||||
quality_metrics['freshness'] = self._calculate_freshness_score_from_age(age)
|
||||
except Exception:
|
||||
quality_metrics['freshness'] = 0.5
|
||||
|
||||
# Accuracy assessment
|
||||
accuracy_score = 0.0
|
||||
if session_data.get('session_id') and isinstance(session_data['session_id'], str):
|
||||
accuracy_score += 0.25
|
||||
if session_data.get('completion_percentage') and isinstance(session_data['completion_percentage'], (int, float)):
|
||||
accuracy_score += 0.25
|
||||
if session_data.get('completed_steps') and isinstance(session_data['completed_steps'], (list, int)):
|
||||
accuracy_score += 0.25
|
||||
if session_data.get('current_step') and isinstance(session_data['current_step'], (str, int)):
|
||||
accuracy_score += 0.25
|
||||
quality_metrics['accuracy'] = accuracy_score
|
||||
|
||||
# Relevance assessment
|
||||
relevance_score = 0.0
|
||||
if session_data.get('completion_percentage', 0) > 50:
|
||||
relevance_score += 0.5
|
||||
if session_data.get('session_data'):
|
||||
relevance_score += 0.5
|
||||
quality_metrics['relevance'] = relevance_score
|
||||
|
||||
# Consistency assessment
|
||||
consistency_score = 0.0
|
||||
if session_data.get('completion_percentage') and session_data.get('completed_steps'):
|
||||
consistency_score += 0.5
|
||||
if session_data.get('current_step') and session_data.get('session_id'):
|
||||
consistency_score += 0.5
|
||||
quality_metrics['consistency'] = consistency_score
|
||||
|
||||
return quality_metrics
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error assessing onboarding session quality: {str(e)}")
|
||||
return {'completeness': 0.0, 'freshness': 0.0, 'accuracy': 0.0, 'relevance': 0.0, 'consistency': 0.0}
|
||||
|
||||
def _calculate_completeness_score(self, website_quality: Dict, research_quality: Dict, api_quality: Dict, session_quality: Dict) -> float:
|
||||
"""Calculate overall completeness score."""
|
||||
try:
|
||||
scores = [
|
||||
website_quality['completeness'],
|
||||
research_quality['completeness'],
|
||||
api_quality['completeness'],
|
||||
session_quality['completeness']
|
||||
]
|
||||
return sum(scores) / len(scores)
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating completeness score: {str(e)}")
|
||||
return 0.0
|
||||
|
||||
def _calculate_freshness_score(self, website_quality: Dict, research_quality: Dict, api_quality: Dict, session_quality: Dict) -> float:
|
||||
"""Calculate overall freshness score."""
|
||||
try:
|
||||
scores = [
|
||||
website_quality['freshness'],
|
||||
research_quality['freshness'],
|
||||
api_quality['freshness'],
|
||||
session_quality['freshness']
|
||||
]
|
||||
return sum(scores) / len(scores)
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating freshness score: {str(e)}")
|
||||
return 0.0
|
||||
|
||||
def _calculate_accuracy_score(self, website_quality: Dict, research_quality: Dict, api_quality: Dict, session_quality: Dict) -> float:
|
||||
"""Calculate overall accuracy score."""
|
||||
try:
|
||||
scores = [
|
||||
website_quality['accuracy'],
|
||||
research_quality['accuracy'],
|
||||
api_quality['accuracy'],
|
||||
session_quality['accuracy']
|
||||
]
|
||||
return sum(scores) / len(scores)
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating accuracy score: {str(e)}")
|
||||
return 0.0
|
||||
|
||||
def _calculate_relevance_score(self, website_quality: Dict, research_quality: Dict, api_quality: Dict, session_quality: Dict) -> float:
|
||||
"""Calculate overall relevance score."""
|
||||
try:
|
||||
scores = [
|
||||
website_quality['relevance'],
|
||||
research_quality['relevance'],
|
||||
api_quality['relevance'],
|
||||
session_quality['relevance']
|
||||
]
|
||||
return sum(scores) / len(scores)
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating relevance score: {str(e)}")
|
||||
return 0.0
|
||||
|
||||
def _calculate_consistency_score(self, website_quality: Dict, research_quality: Dict, api_quality: Dict, session_quality: Dict) -> float:
|
||||
"""Calculate overall consistency score."""
|
||||
try:
|
||||
scores = [
|
||||
website_quality['consistency'],
|
||||
research_quality['consistency'],
|
||||
api_quality['consistency'],
|
||||
session_quality['consistency']
|
||||
]
|
||||
return sum(scores) / len(scores)
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating consistency score: {str(e)}")
|
||||
return 0.0
|
||||
|
||||
def _calculate_freshness_score_from_age(self, age: timedelta) -> float:
|
||||
"""Calculate freshness score based on data age."""
|
||||
try:
|
||||
if age <= self.data_freshness_threshold:
|
||||
return 1.0
|
||||
elif age <= self.max_data_age:
|
||||
# Linear decay from 1.0 to 0.5
|
||||
decay_factor = 1.0 - (age - self.data_freshness_threshold) / (self.max_data_age - self.data_freshness_threshold) * 0.5
|
||||
return max(0.5, decay_factor)
|
||||
else:
|
||||
return 0.5 # Minimum freshness for old data
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating freshness score from age: {str(e)}")
|
||||
return 0.5
|
||||
|
||||
def _determine_quality_level(self, overall_score: float) -> str:
|
||||
"""Determine quality level based on overall score."""
|
||||
try:
|
||||
if overall_score >= self.quality_thresholds['excellent']:
|
||||
return 'excellent'
|
||||
elif overall_score >= self.quality_thresholds['good']:
|
||||
return 'good'
|
||||
elif overall_score >= self.quality_thresholds['fair']:
|
||||
return 'fair'
|
||||
else:
|
||||
return 'poor'
|
||||
except Exception as e:
|
||||
logger.error(f"Error determining quality level: {str(e)}")
|
||||
return 'poor'
|
||||
|
||||
def _generate_quality_recommendations(self, quality_assessment: Dict[str, Any]) -> List[str]:
|
||||
"""Generate recommendations based on quality assessment."""
|
||||
try:
|
||||
recommendations = []
|
||||
|
||||
if quality_assessment['completeness'] < 0.7:
|
||||
recommendations.append("Complete missing onboarding data to improve strategy accuracy")
|
||||
|
||||
if quality_assessment['freshness'] < 0.7:
|
||||
recommendations.append("Update stale data to ensure current market insights")
|
||||
|
||||
if quality_assessment['accuracy'] < 0.7:
|
||||
recommendations.append("Verify data accuracy for better strategy recommendations")
|
||||
|
||||
if quality_assessment['relevance'] < 0.7:
|
||||
recommendations.append("Provide more relevant data for targeted strategy development")
|
||||
|
||||
if quality_assessment['consistency'] < 0.7:
|
||||
recommendations.append("Ensure data consistency across different sources")
|
||||
|
||||
if quality_assessment['overall_score'] < 0.5:
|
||||
recommendations.append("Consider re-running onboarding process for better data quality")
|
||||
|
||||
return recommendations
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating quality recommendations: {str(e)}")
|
||||
return ["Unable to generate recommendations due to assessment error"]
|
||||
|
||||
def _identify_quality_issues(self, quality_assessment: Dict[str, Any]) -> List[str]:
|
||||
"""Identify specific quality issues."""
|
||||
try:
|
||||
issues = []
|
||||
|
||||
if quality_assessment['completeness'] < 0.5:
|
||||
issues.append("Incomplete data: Missing critical onboarding information")
|
||||
|
||||
if quality_assessment['freshness'] < 0.5:
|
||||
issues.append("Stale data: Information may be outdated")
|
||||
|
||||
if quality_assessment['accuracy'] < 0.5:
|
||||
issues.append("Data accuracy concerns: Verify information validity")
|
||||
|
||||
if quality_assessment['relevance'] < 0.5:
|
||||
issues.append("Low relevance: Data may not align with current needs")
|
||||
|
||||
if quality_assessment['consistency'] < 0.5:
|
||||
issues.append("Inconsistent data: Conflicting information detected")
|
||||
|
||||
return issues
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error identifying quality issues: {str(e)}")
|
||||
return ["Unable to identify issues due to assessment error"]
|
||||
|
||||
def validate_field_data(self, field_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate individual field data."""
|
||||
try:
|
||||
validation_result = {
|
||||
'is_valid': True,
|
||||
'errors': [],
|
||||
'warnings': [],
|
||||
'confidence': 1.0
|
||||
}
|
||||
|
||||
for field_name, field_value in field_data.items():
|
||||
if field_value is None or field_value == '':
|
||||
validation_result['errors'].append(f"Field '{field_name}' is empty")
|
||||
validation_result['is_valid'] = False
|
||||
elif isinstance(field_value, str) and len(field_value.strip()) < 3:
|
||||
validation_result['warnings'].append(f"Field '{field_name}' may be too short")
|
||||
validation_result['confidence'] *= 0.9
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating field data: {str(e)}")
|
||||
return {
|
||||
'is_valid': False,
|
||||
'errors': ['Validation failed'],
|
||||
'warnings': [],
|
||||
'confidence': 0.0
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Performance Module
|
||||
Caching, optimization, and health monitoring services.
|
||||
"""
|
||||
|
||||
from .caching import CachingService
|
||||
from .optimization import PerformanceOptimizationService
|
||||
from .health_monitoring import HealthMonitoringService
|
||||
|
||||
__all__ = ['CachingService', 'PerformanceOptimizationService', 'HealthMonitoringService']
|
||||
@@ -0,0 +1,469 @@
|
||||
"""
|
||||
Caching Service
|
||||
Cache management and optimization.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import json
|
||||
import hashlib
|
||||
from typing import Dict, Any, Optional, List
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Try to import Redis, fallback to in-memory if not available
|
||||
try:
|
||||
import redis
|
||||
REDIS_AVAILABLE = True
|
||||
except ImportError:
|
||||
REDIS_AVAILABLE = False
|
||||
logger.warning("Redis not available, using in-memory caching")
|
||||
|
||||
class CachingService:
|
||||
"""Service for intelligent caching of content strategy data."""
|
||||
|
||||
def __init__(self):
|
||||
# Cache configuration
|
||||
self.cache_config = {
|
||||
'ai_analysis': {
|
||||
'ttl': 3600, # 1 hour
|
||||
'max_size': 1000,
|
||||
'priority': 'high'
|
||||
},
|
||||
'onboarding_data': {
|
||||
'ttl': 1800, # 30 minutes
|
||||
'max_size': 500,
|
||||
'priority': 'medium'
|
||||
},
|
||||
'strategy_cache': {
|
||||
'ttl': 7200, # 2 hours
|
||||
'max_size': 200,
|
||||
'priority': 'high'
|
||||
},
|
||||
'field_transformations': {
|
||||
'ttl': 900, # 15 minutes
|
||||
'max_size': 1000,
|
||||
'priority': 'low'
|
||||
}
|
||||
}
|
||||
|
||||
# Initialize Redis connection if available
|
||||
self.redis_available = False
|
||||
if REDIS_AVAILABLE:
|
||||
try:
|
||||
self.redis_client = redis.Redis(
|
||||
host='localhost',
|
||||
port=6379,
|
||||
db=0,
|
||||
decode_responses=True,
|
||||
socket_connect_timeout=5,
|
||||
socket_timeout=5
|
||||
)
|
||||
# Test connection
|
||||
self.redis_client.ping()
|
||||
self.redis_available = True
|
||||
logger.info("Redis connection established successfully")
|
||||
except Exception as e:
|
||||
logger.warning(f"Redis connection failed: {str(e)}. Using in-memory cache.")
|
||||
self.redis_available = False
|
||||
self.memory_cache = {}
|
||||
else:
|
||||
logger.info("Using in-memory cache (Redis not available)")
|
||||
self.memory_cache = {}
|
||||
|
||||
def get_cache_key(self, cache_type: str, identifier: str, **kwargs) -> str:
|
||||
"""Generate a unique cache key."""
|
||||
try:
|
||||
# Create a hash of the identifier and additional parameters
|
||||
key_data = f"{cache_type}:{identifier}"
|
||||
if kwargs:
|
||||
key_data += ":" + json.dumps(kwargs, sort_keys=True)
|
||||
|
||||
# Create hash for consistent key length
|
||||
key_hash = hashlib.md5(key_data.encode()).hexdigest()
|
||||
return f"content_strategy:{cache_type}:{key_hash}"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating cache key: {str(e)}")
|
||||
return f"content_strategy:{cache_type}:{identifier}"
|
||||
|
||||
async def get_cached_data(self, cache_type: str, identifier: str, **kwargs) -> Optional[Dict[str, Any]]:
|
||||
"""Retrieve cached data."""
|
||||
try:
|
||||
if not self.redis_available:
|
||||
return self._get_from_memory_cache(cache_type, identifier, **kwargs)
|
||||
|
||||
cache_key = self.get_cache_key(cache_type, identifier, **kwargs)
|
||||
cached_data = self.redis_client.get(cache_key)
|
||||
|
||||
if cached_data:
|
||||
data = json.loads(cached_data)
|
||||
logger.info(f"Cache hit for {cache_type}:{identifier}")
|
||||
return data
|
||||
else:
|
||||
logger.info(f"Cache miss for {cache_type}:{identifier}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error retrieving cached data: {str(e)}")
|
||||
return None
|
||||
|
||||
async def set_cached_data(self, cache_type: str, identifier: str, data: Dict[str, Any], **kwargs) -> bool:
|
||||
"""Store data in cache."""
|
||||
try:
|
||||
if not self.redis_available:
|
||||
return self._set_in_memory_cache(cache_type, identifier, data, **kwargs)
|
||||
|
||||
cache_key = self.get_cache_key(cache_type, identifier, **kwargs)
|
||||
ttl = self.cache_config.get(cache_type, {}).get('ttl', 3600)
|
||||
|
||||
# Add metadata to cached data
|
||||
cached_data = {
|
||||
'data': data,
|
||||
'metadata': {
|
||||
'cached_at': datetime.utcnow().isoformat(),
|
||||
'cache_type': cache_type,
|
||||
'identifier': identifier,
|
||||
'ttl': ttl
|
||||
}
|
||||
}
|
||||
|
||||
# Store in Redis with TTL
|
||||
result = self.redis_client.setex(
|
||||
cache_key,
|
||||
ttl,
|
||||
json.dumps(cached_data, default=str)
|
||||
)
|
||||
|
||||
if result:
|
||||
logger.info(f"Data cached successfully for {cache_type}:{identifier}")
|
||||
await self._update_cache_stats(cache_type, 'set')
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"Failed to cache data for {cache_type}:{identifier}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting cached data: {str(e)}")
|
||||
return False
|
||||
|
||||
async def invalidate_cache(self, cache_type: str, identifier: str, **kwargs) -> bool:
|
||||
"""Invalidate specific cached data."""
|
||||
try:
|
||||
if not self.redis_available:
|
||||
return self._invalidate_memory_cache(cache_type, identifier, **kwargs)
|
||||
|
||||
cache_key = self.get_cache_key(cache_type, identifier, **kwargs)
|
||||
result = self.redis_client.delete(cache_key)
|
||||
|
||||
if result:
|
||||
logger.info(f"Cache invalidated for {cache_type}:{identifier}")
|
||||
await self._update_cache_stats(cache_type, 'invalidate')
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"No cache entry found to invalidate for {cache_type}:{identifier}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error invalidating cache: {str(e)}")
|
||||
return False
|
||||
|
||||
async def clear_cache_type(self, cache_type: str) -> bool:
|
||||
"""Clear all cached data of a specific type."""
|
||||
try:
|
||||
if not self.redis_available:
|
||||
return self._clear_memory_cache_type(cache_type)
|
||||
|
||||
pattern = f"content_strategy:{cache_type}:*"
|
||||
keys = self.redis_client.keys(pattern)
|
||||
|
||||
if keys:
|
||||
result = self.redis_client.delete(*keys)
|
||||
logger.info(f"Cleared {result} cache entries for {cache_type}")
|
||||
await self._update_cache_stats(cache_type, 'clear')
|
||||
return True
|
||||
else:
|
||||
logger.info(f"No cache entries found for {cache_type}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error clearing cache type {cache_type}: {str(e)}")
|
||||
return False
|
||||
|
||||
async def get_cache_stats(self, cache_type: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Get cache statistics."""
|
||||
try:
|
||||
if not self.redis_available:
|
||||
return self._get_memory_cache_stats(cache_type)
|
||||
|
||||
stats = {}
|
||||
|
||||
if cache_type:
|
||||
pattern = f"content_strategy:{cache_type}:*"
|
||||
keys = self.redis_client.keys(pattern)
|
||||
stats[cache_type] = {
|
||||
'entries': len(keys),
|
||||
'size_bytes': sum(len(self.redis_client.get(key) or '') for key in keys),
|
||||
'config': self.cache_config.get(cache_type, {})
|
||||
}
|
||||
else:
|
||||
for cache_type_name in self.cache_config.keys():
|
||||
pattern = f"content_strategy:{cache_type_name}:*"
|
||||
keys = self.redis_client.keys(pattern)
|
||||
stats[cache_type_name] = {
|
||||
'entries': len(keys),
|
||||
'size_bytes': sum(len(self.redis_client.get(key) or '') for key in keys),
|
||||
'config': self.cache_config.get(cache_type_name, {})
|
||||
}
|
||||
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting cache stats: {str(e)}")
|
||||
return {}
|
||||
|
||||
async def optimize_cache(self) -> Dict[str, Any]:
|
||||
"""Optimize cache by removing expired entries and managing memory."""
|
||||
try:
|
||||
if not self.redis_available:
|
||||
return self._optimize_memory_cache()
|
||||
|
||||
optimization_results = {}
|
||||
|
||||
for cache_type, config in self.cache_config.items():
|
||||
pattern = f"content_strategy:{cache_type}:*"
|
||||
keys = self.redis_client.keys(pattern)
|
||||
|
||||
if len(keys) > config.get('max_size', 1000):
|
||||
# Remove oldest entries to maintain max size
|
||||
keys_with_times = []
|
||||
for key in keys:
|
||||
ttl = self.redis_client.ttl(key)
|
||||
if ttl > 0: # Key still has TTL
|
||||
keys_with_times.append((key, ttl))
|
||||
|
||||
# Sort by TTL (oldest first)
|
||||
keys_with_times.sort(key=lambda x: x[1])
|
||||
|
||||
# Remove excess entries
|
||||
excess_count = len(keys) - config.get('max_size', 1000)
|
||||
keys_to_remove = [key for key, _ in keys_with_times[:excess_count]]
|
||||
|
||||
if keys_to_remove:
|
||||
removed_count = self.redis_client.delete(*keys_to_remove)
|
||||
optimization_results[cache_type] = {
|
||||
'entries_removed': removed_count,
|
||||
'reason': 'max_size_exceeded'
|
||||
}
|
||||
logger.info(f"Optimized {cache_type} cache: removed {removed_count} entries")
|
||||
|
||||
return optimization_results
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error optimizing cache: {str(e)}")
|
||||
return {}
|
||||
|
||||
async def _update_cache_stats(self, cache_type: str, operation: str) -> None:
|
||||
"""Update cache statistics."""
|
||||
try:
|
||||
if not self.redis_available:
|
||||
return
|
||||
|
||||
stats_key = f"cache_stats:{cache_type}"
|
||||
current_stats = self.redis_client.hgetall(stats_key)
|
||||
|
||||
# Update operation counts
|
||||
current_stats[f"{operation}_count"] = str(int(current_stats.get(f"{operation}_count", 0)) + 1)
|
||||
current_stats['last_updated'] = datetime.utcnow().isoformat()
|
||||
|
||||
# Store updated stats
|
||||
self.redis_client.hset(stats_key, mapping=current_stats)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating cache stats: {str(e)}")
|
||||
|
||||
# Memory cache fallback methods
|
||||
def _get_from_memory_cache(self, cache_type: str, identifier: str, **kwargs) -> Optional[Dict[str, Any]]:
|
||||
"""Get data from memory cache."""
|
||||
try:
|
||||
cache_key = self.get_cache_key(cache_type, identifier, **kwargs)
|
||||
cached_data = self.memory_cache.get(cache_key)
|
||||
|
||||
if cached_data:
|
||||
# Check if data is still valid
|
||||
cached_at = datetime.fromisoformat(cached_data['metadata']['cached_at'])
|
||||
ttl = cached_data['metadata']['ttl']
|
||||
|
||||
if datetime.utcnow() - cached_at < timedelta(seconds=ttl):
|
||||
logger.info(f"Memory cache hit for {cache_type}:{identifier}")
|
||||
return cached_data['data']
|
||||
else:
|
||||
# Remove expired entry
|
||||
del self.memory_cache[cache_key]
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting from memory cache: {str(e)}")
|
||||
return None
|
||||
|
||||
def _set_in_memory_cache(self, cache_type: str, identifier: str, data: Dict[str, Any], **kwargs) -> bool:
|
||||
"""Set data in memory cache."""
|
||||
try:
|
||||
cache_key = self.get_cache_key(cache_type, identifier, **kwargs)
|
||||
ttl = self.cache_config.get(cache_type, {}).get('ttl', 3600)
|
||||
|
||||
cached_data = {
|
||||
'data': data,
|
||||
'metadata': {
|
||||
'cached_at': datetime.utcnow().isoformat(),
|
||||
'cache_type': cache_type,
|
||||
'identifier': identifier,
|
||||
'ttl': ttl
|
||||
}
|
||||
}
|
||||
|
||||
# Check max size and remove oldest if needed
|
||||
max_size = self.cache_config.get(cache_type, {}).get('max_size', 1000)
|
||||
if len(self.memory_cache) >= max_size:
|
||||
# Remove oldest entry
|
||||
oldest_key = min(self.memory_cache.keys(),
|
||||
key=lambda k: self.memory_cache[k]['metadata']['cached_at'])
|
||||
del self.memory_cache[oldest_key]
|
||||
|
||||
self.memory_cache[cache_key] = cached_data
|
||||
logger.info(f"Data cached in memory for {cache_type}:{identifier}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting in memory cache: {str(e)}")
|
||||
return False
|
||||
|
||||
def _invalidate_memory_cache(self, cache_type: str, identifier: str, **kwargs) -> bool:
|
||||
"""Invalidate memory cache entry."""
|
||||
try:
|
||||
cache_key = self.get_cache_key(cache_type, identifier, **kwargs)
|
||||
if cache_key in self.memory_cache:
|
||||
del self.memory_cache[cache_key]
|
||||
logger.info(f"Memory cache invalidated for {cache_type}:{identifier}")
|
||||
return True
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error invalidating memory cache: {str(e)}")
|
||||
return False
|
||||
|
||||
def _clear_memory_cache_type(self, cache_type: str) -> bool:
|
||||
"""Clear memory cache by type."""
|
||||
try:
|
||||
keys_to_remove = [key for key in self.memory_cache.keys()
|
||||
if key.startswith(f"content_strategy:{cache_type}:")]
|
||||
|
||||
for key in keys_to_remove:
|
||||
del self.memory_cache[key]
|
||||
|
||||
logger.info(f"Cleared {len(keys_to_remove)} memory cache entries for {cache_type}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error clearing memory cache type: {str(e)}")
|
||||
return False
|
||||
|
||||
def _get_memory_cache_stats(self, cache_type: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Get memory cache statistics."""
|
||||
try:
|
||||
stats = {}
|
||||
|
||||
if cache_type:
|
||||
keys = [key for key in self.memory_cache.keys()
|
||||
if key.startswith(f"content_strategy:{cache_type}:")]
|
||||
stats[cache_type] = {
|
||||
'entries': len(keys),
|
||||
'size_bytes': sum(len(str(value)) for value in [self.memory_cache[key] for key in keys]),
|
||||
'config': self.cache_config.get(cache_type, {})
|
||||
}
|
||||
else:
|
||||
for cache_type_name in self.cache_config.keys():
|
||||
keys = [key for key in self.memory_cache.keys()
|
||||
if key.startswith(f"content_strategy:{cache_type_name}:")]
|
||||
stats[cache_type_name] = {
|
||||
'entries': len(keys),
|
||||
'size_bytes': sum(len(str(value)) for value in [self.memory_cache[key] for key in keys]),
|
||||
'config': self.cache_config.get(cache_type_name, {})
|
||||
}
|
||||
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting memory cache stats: {str(e)}")
|
||||
return {}
|
||||
|
||||
def _optimize_memory_cache(self) -> Dict[str, Any]:
|
||||
"""Optimize memory cache."""
|
||||
try:
|
||||
optimization_results = {}
|
||||
|
||||
for cache_type, config in self.cache_config.items():
|
||||
keys = [key for key in self.memory_cache.keys()
|
||||
if key.startswith(f"content_strategy:{cache_type}:")]
|
||||
|
||||
if len(keys) > config.get('max_size', 1000):
|
||||
# Remove oldest entries
|
||||
keys_with_times = []
|
||||
for key in keys:
|
||||
cached_at = datetime.fromisoformat(self.memory_cache[key]['metadata']['cached_at'])
|
||||
keys_with_times.append((key, cached_at))
|
||||
|
||||
# Sort by cached time (oldest first)
|
||||
keys_with_times.sort(key=lambda x: x[1])
|
||||
|
||||
# Remove excess entries
|
||||
excess_count = len(keys) - config.get('max_size', 1000)
|
||||
keys_to_remove = [key for key, _ in keys_with_times[:excess_count]]
|
||||
|
||||
for key in keys_to_remove:
|
||||
del self.memory_cache[key]
|
||||
|
||||
optimization_results[cache_type] = {
|
||||
'entries_removed': len(keys_to_remove),
|
||||
'reason': 'max_size_exceeded'
|
||||
}
|
||||
|
||||
return optimization_results
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error optimizing memory cache: {str(e)}")
|
||||
return {}
|
||||
|
||||
# Cache-specific methods for different data types
|
||||
async def cache_ai_analysis(self, user_id: int, analysis_type: str, analysis_data: Dict[str, Any]) -> bool:
|
||||
"""Cache AI analysis results."""
|
||||
return await self.set_cached_data('ai_analysis', f"{user_id}:{analysis_type}", analysis_data)
|
||||
|
||||
async def get_cached_ai_analysis(self, user_id: int, analysis_type: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get cached AI analysis results."""
|
||||
return await self.get_cached_data('ai_analysis', f"{user_id}:{analysis_type}")
|
||||
|
||||
async def cache_onboarding_data(self, user_id: int, onboarding_data: Dict[str, Any]) -> bool:
|
||||
"""Cache onboarding data."""
|
||||
return await self.set_cached_data('onboarding_data', str(user_id), onboarding_data)
|
||||
|
||||
async def get_cached_onboarding_data(self, user_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Get cached onboarding data."""
|
||||
return await self.get_cached_data('onboarding_data', str(user_id))
|
||||
|
||||
async def cache_strategy(self, strategy_id: int, strategy_data: Dict[str, Any]) -> bool:
|
||||
"""Cache strategy data."""
|
||||
return await self.set_cached_data('strategy_cache', str(strategy_id), strategy_data)
|
||||
|
||||
async def get_cached_strategy(self, strategy_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Get cached strategy data."""
|
||||
return await self.get_cached_data('strategy_cache', str(strategy_id))
|
||||
|
||||
async def cache_field_transformations(self, user_id: int, transformations: Dict[str, Any]) -> bool:
|
||||
"""Cache field transformations."""
|
||||
return await self.set_cached_data('field_transformations', str(user_id), transformations)
|
||||
|
||||
async def get_cached_field_transformations(self, user_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Get cached field transformations."""
|
||||
return await self.get_cached_data('field_transformations', str(user_id))
|
||||
@@ -0,0 +1,594 @@
|
||||
"""
|
||||
Health Monitoring Service
|
||||
System health monitoring and performance tracking.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import asyncio
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import text
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class HealthMonitoringService:
|
||||
"""Service for system health monitoring and assessment."""
|
||||
|
||||
def __init__(self):
|
||||
self.health_thresholds = {
|
||||
'database_response_time': 1.0, # seconds
|
||||
'cache_response_time': 0.1, # seconds
|
||||
'ai_service_response_time': 5.0, # seconds
|
||||
'memory_usage_threshold': 80, # percentage
|
||||
'cpu_usage_threshold': 80, # percentage
|
||||
'disk_usage_threshold': 90, # percentage
|
||||
'error_rate_threshold': 0.05 # 5%
|
||||
}
|
||||
|
||||
self.health_status = {
|
||||
'timestamp': None,
|
||||
'overall_status': 'healthy',
|
||||
'components': {},
|
||||
'alerts': [],
|
||||
'recommendations': []
|
||||
}
|
||||
|
||||
async def check_system_health(self, db: Session, cache_service=None, ai_service=None) -> Dict[str, Any]:
|
||||
"""Perform comprehensive system health check."""
|
||||
try:
|
||||
logger.info("Starting comprehensive system health check")
|
||||
|
||||
health_report = {
|
||||
'timestamp': datetime.utcnow().isoformat(),
|
||||
'overall_status': 'healthy',
|
||||
'components': {},
|
||||
'alerts': [],
|
||||
'recommendations': []
|
||||
}
|
||||
|
||||
# Check database health
|
||||
db_health = await self._check_database_health(db)
|
||||
health_report['components']['database'] = db_health
|
||||
|
||||
# Check cache health
|
||||
if cache_service:
|
||||
cache_health = await self._check_cache_health(cache_service)
|
||||
health_report['components']['cache'] = cache_health
|
||||
else:
|
||||
health_report['components']['cache'] = {'status': 'not_available', 'message': 'Cache service not provided'}
|
||||
|
||||
# Check AI service health
|
||||
if ai_service:
|
||||
ai_health = await self._check_ai_service_health(ai_service)
|
||||
health_report['components']['ai_service'] = ai_health
|
||||
else:
|
||||
health_report['components']['ai_service'] = {'status': 'not_available', 'message': 'AI service not provided'}
|
||||
|
||||
# Check system resources
|
||||
system_health = await self._check_system_resources()
|
||||
health_report['components']['system'] = system_health
|
||||
|
||||
# Determine overall status
|
||||
health_report['overall_status'] = self._determine_overall_health(health_report['components'])
|
||||
|
||||
# Generate alerts and recommendations
|
||||
health_report['alerts'] = self._generate_health_alerts(health_report['components'])
|
||||
health_report['recommendations'] = await self._generate_health_recommendations(health_report['components'])
|
||||
|
||||
# Update health status
|
||||
self.health_status = health_report
|
||||
|
||||
logger.info(f"System health check completed. Overall status: {health_report['overall_status']}")
|
||||
return health_report
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during system health check: {str(e)}")
|
||||
return {
|
||||
'timestamp': datetime.utcnow().isoformat(),
|
||||
'overall_status': 'error',
|
||||
'components': {},
|
||||
'alerts': [f'Health check failed: {str(e)}'],
|
||||
'recommendations': ['Investigate health check system']
|
||||
}
|
||||
|
||||
async def _check_database_health(self, db: Session) -> Dict[str, Any]:
|
||||
"""Check database health and performance."""
|
||||
try:
|
||||
start_time = time.time()
|
||||
|
||||
# Test database connection
|
||||
try:
|
||||
result = db.execute(text("SELECT 1"))
|
||||
result.fetchone()
|
||||
connection_status = 'healthy'
|
||||
except Exception as e:
|
||||
connection_status = 'unhealthy'
|
||||
logger.error(f"Database connection test failed: {str(e)}")
|
||||
|
||||
# Test query performance
|
||||
try:
|
||||
query_start = time.time()
|
||||
result = db.execute(text("SELECT COUNT(*) FROM information_schema.tables"))
|
||||
result.fetchone()
|
||||
query_time = time.time() - query_start
|
||||
query_status = 'healthy' if query_time <= self.health_thresholds['database_response_time'] else 'degraded'
|
||||
except Exception as e:
|
||||
query_time = 0
|
||||
query_status = 'unhealthy'
|
||||
logger.error(f"Database query test failed: {str(e)}")
|
||||
|
||||
# Check database size and performance
|
||||
try:
|
||||
# Get database statistics
|
||||
db_stats = await self._get_database_statistics(db)
|
||||
except Exception as e:
|
||||
db_stats = {'error': str(e)}
|
||||
|
||||
total_time = time.time() - start_time
|
||||
|
||||
return {
|
||||
'status': 'healthy' if connection_status == 'healthy' and query_status == 'healthy' else 'degraded',
|
||||
'connection_status': connection_status,
|
||||
'query_status': query_status,
|
||||
'response_time': query_time,
|
||||
'total_check_time': total_time,
|
||||
'statistics': db_stats,
|
||||
'last_checked': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking database health: {str(e)}")
|
||||
return {
|
||||
'status': 'unhealthy',
|
||||
'error': str(e),
|
||||
'last_checked': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
async def _check_cache_health(self, cache_service) -> Dict[str, Any]:
|
||||
"""Check cache health and performance."""
|
||||
try:
|
||||
start_time = time.time()
|
||||
|
||||
# Test cache connectivity
|
||||
try:
|
||||
cache_stats = await cache_service.get_cache_stats()
|
||||
connectivity_status = 'healthy'
|
||||
except Exception as e:
|
||||
cache_stats = {}
|
||||
connectivity_status = 'unhealthy'
|
||||
logger.error(f"Cache connectivity test failed: {str(e)}")
|
||||
|
||||
# Test cache performance
|
||||
try:
|
||||
test_key = f"health_check_{int(time.time())}"
|
||||
test_data = {'test': 'data', 'timestamp': datetime.utcnow().isoformat()}
|
||||
|
||||
# Test write
|
||||
write_start = time.time()
|
||||
write_success = await cache_service.set_cached_data('health_check', test_key, test_data)
|
||||
write_time = time.time() - write_start
|
||||
|
||||
# Test read
|
||||
read_start = time.time()
|
||||
read_data = await cache_service.get_cached_data('health_check', test_key)
|
||||
read_time = time.time() - read_start
|
||||
|
||||
# Clean up
|
||||
await cache_service.invalidate_cache('health_check', test_key)
|
||||
|
||||
performance_status = 'healthy' if write_success and read_data and (write_time + read_time) <= self.health_thresholds['cache_response_time'] else 'degraded'
|
||||
|
||||
except Exception as e:
|
||||
write_time = 0
|
||||
read_time = 0
|
||||
performance_status = 'unhealthy'
|
||||
logger.error(f"Cache performance test failed: {str(e)}")
|
||||
|
||||
total_time = time.time() - start_time
|
||||
|
||||
return {
|
||||
'status': 'healthy' if connectivity_status == 'healthy' and performance_status == 'healthy' else 'degraded',
|
||||
'connectivity_status': connectivity_status,
|
||||
'performance_status': performance_status,
|
||||
'write_time': write_time,
|
||||
'read_time': read_time,
|
||||
'total_check_time': total_time,
|
||||
'statistics': cache_stats,
|
||||
'last_checked': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking cache health: {str(e)}")
|
||||
return {
|
||||
'status': 'unhealthy',
|
||||
'error': str(e),
|
||||
'last_checked': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
async def _check_ai_service_health(self, ai_service) -> Dict[str, Any]:
|
||||
"""Check AI service health and performance."""
|
||||
try:
|
||||
start_time = time.time()
|
||||
|
||||
# Test AI service connectivity
|
||||
try:
|
||||
# Simple test call to AI service
|
||||
test_prompt = "Test health check"
|
||||
ai_start = time.time()
|
||||
ai_response = await ai_service._call_ai_service(test_prompt, 'health_check')
|
||||
ai_time = time.time() - ai_start
|
||||
|
||||
connectivity_status = 'healthy' if ai_response else 'unhealthy'
|
||||
performance_status = 'healthy' if ai_time <= self.health_thresholds['ai_service_response_time'] else 'degraded'
|
||||
|
||||
except Exception as e:
|
||||
ai_time = 0
|
||||
connectivity_status = 'unhealthy'
|
||||
performance_status = 'unhealthy'
|
||||
logger.error(f"AI service health check failed: {str(e)}")
|
||||
|
||||
total_time = time.time() - start_time
|
||||
|
||||
return {
|
||||
'status': 'healthy' if connectivity_status == 'healthy' and performance_status == 'healthy' else 'degraded',
|
||||
'connectivity_status': connectivity_status,
|
||||
'performance_status': performance_status,
|
||||
'response_time': ai_time,
|
||||
'total_check_time': total_time,
|
||||
'last_checked': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking AI service health: {str(e)}")
|
||||
return {
|
||||
'status': 'unhealthy',
|
||||
'error': str(e),
|
||||
'last_checked': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
async def _check_system_resources(self) -> Dict[str, Any]:
|
||||
"""Check system resource usage."""
|
||||
try:
|
||||
import psutil
|
||||
|
||||
# CPU usage
|
||||
cpu_percent = psutil.cpu_percent(interval=1)
|
||||
cpu_status = 'healthy' if cpu_percent <= self.health_thresholds['cpu_usage_threshold'] else 'degraded'
|
||||
|
||||
# Memory usage
|
||||
memory = psutil.virtual_memory()
|
||||
memory_percent = memory.percent
|
||||
memory_status = 'healthy' if memory_percent <= self.health_thresholds['memory_usage_threshold'] else 'degraded'
|
||||
|
||||
# Disk usage
|
||||
disk = psutil.disk_usage('/')
|
||||
disk_percent = disk.percent
|
||||
disk_status = 'healthy' if disk_percent <= self.health_thresholds['disk_usage_threshold'] else 'degraded'
|
||||
|
||||
# Network status
|
||||
try:
|
||||
network = psutil.net_io_counters()
|
||||
network_status = 'healthy'
|
||||
except Exception:
|
||||
network_status = 'degraded'
|
||||
|
||||
return {
|
||||
'status': 'healthy' if all(s == 'healthy' for s in [cpu_status, memory_status, disk_status, network_status]) else 'degraded',
|
||||
'cpu': {
|
||||
'usage_percent': cpu_percent,
|
||||
'status': cpu_status
|
||||
},
|
||||
'memory': {
|
||||
'usage_percent': memory_percent,
|
||||
'available_gb': memory.available / (1024**3),
|
||||
'total_gb': memory.total / (1024**3),
|
||||
'status': memory_status
|
||||
},
|
||||
'disk': {
|
||||
'usage_percent': disk_percent,
|
||||
'free_gb': disk.free / (1024**3),
|
||||
'total_gb': disk.total / (1024**3),
|
||||
'status': disk_status
|
||||
},
|
||||
'network': {
|
||||
'status': network_status
|
||||
},
|
||||
'last_checked': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking system resources: {str(e)}")
|
||||
return {
|
||||
'status': 'unhealthy',
|
||||
'error': str(e),
|
||||
'last_checked': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
async def _get_database_statistics(self, db: Session) -> Dict[str, Any]:
|
||||
"""Get database statistics."""
|
||||
try:
|
||||
stats = {}
|
||||
|
||||
# Get table counts (simplified)
|
||||
try:
|
||||
result = db.execute(text("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public'"))
|
||||
stats['table_count'] = result.fetchone()[0]
|
||||
except Exception:
|
||||
stats['table_count'] = 'unknown'
|
||||
|
||||
# Get database size (simplified)
|
||||
try:
|
||||
result = db.execute(text("SELECT pg_size_pretty(pg_database_size(current_database()))"))
|
||||
stats['database_size'] = result.fetchone()[0]
|
||||
except Exception:
|
||||
stats['database_size'] = 'unknown'
|
||||
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting database statistics: {str(e)}")
|
||||
return {'error': str(e)}
|
||||
|
||||
def _determine_overall_health(self, components: Dict[str, Any]) -> str:
|
||||
"""Determine overall system health based on component status."""
|
||||
try:
|
||||
statuses = []
|
||||
for component_name, component_data in components.items():
|
||||
if isinstance(component_data, dict) and 'status' in component_data:
|
||||
statuses.append(component_data['status'])
|
||||
|
||||
if not statuses:
|
||||
return 'unknown'
|
||||
|
||||
if 'unhealthy' in statuses:
|
||||
return 'unhealthy'
|
||||
elif 'degraded' in statuses:
|
||||
return 'degraded'
|
||||
elif all(status == 'healthy' for status in statuses):
|
||||
return 'healthy'
|
||||
else:
|
||||
return 'unknown'
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error determining overall health: {str(e)}")
|
||||
return 'unknown'
|
||||
|
||||
def _generate_health_alerts(self, components: Dict[str, Any]) -> List[str]:
|
||||
"""Generate health alerts based on component status."""
|
||||
try:
|
||||
alerts = []
|
||||
|
||||
for component_name, component_data in components.items():
|
||||
if isinstance(component_data, dict) and 'status' in component_data:
|
||||
status = component_data['status']
|
||||
|
||||
if status == 'unhealthy':
|
||||
alerts.append(f"CRITICAL: {component_name} is unhealthy")
|
||||
elif status == 'degraded':
|
||||
alerts.append(f"WARNING: {component_name} performance is degraded")
|
||||
|
||||
# Component-specific alerts
|
||||
if component_name == 'database' and component_data.get('response_time', 0) > self.health_thresholds['database_response_time']:
|
||||
alerts.append(f"WARNING: Database response time is slow: {component_data['response_time']:.2f}s")
|
||||
|
||||
elif component_name == 'cache' and component_data.get('write_time', 0) + component_data.get('read_time', 0) > self.health_thresholds['cache_response_time']:
|
||||
alerts.append(f"WARNING: Cache response time is slow: {component_data.get('write_time', 0) + component_data.get('read_time', 0):.2f}s")
|
||||
|
||||
elif component_name == 'ai_service' and component_data.get('response_time', 0) > self.health_thresholds['ai_service_response_time']:
|
||||
alerts.append(f"WARNING: AI service response time is slow: {component_data['response_time']:.2f}s")
|
||||
|
||||
elif component_name == 'system':
|
||||
cpu_data = component_data.get('cpu', {})
|
||||
memory_data = component_data.get('memory', {})
|
||||
disk_data = component_data.get('disk', {})
|
||||
|
||||
if cpu_data.get('usage_percent', 0) > self.health_thresholds['cpu_usage_threshold']:
|
||||
alerts.append(f"WARNING: High CPU usage: {cpu_data['usage_percent']:.1f}%")
|
||||
|
||||
if memory_data.get('usage_percent', 0) > self.health_thresholds['memory_usage_threshold']:
|
||||
alerts.append(f"WARNING: High memory usage: {memory_data['usage_percent']:.1f}%")
|
||||
|
||||
if disk_data.get('usage_percent', 0) > self.health_thresholds['disk_usage_threshold']:
|
||||
alerts.append(f"WARNING: High disk usage: {disk_data['usage_percent']:.1f}%")
|
||||
|
||||
return alerts
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating health alerts: {str(e)}")
|
||||
return ['Error generating health alerts']
|
||||
|
||||
async def _generate_health_recommendations(self, components: Dict[str, Any]) -> List[str]:
|
||||
"""Generate health recommendations based on component status."""
|
||||
try:
|
||||
recommendations = []
|
||||
|
||||
for component_name, component_data in components.items():
|
||||
if isinstance(component_data, dict) and 'status' in component_data:
|
||||
status = component_data['status']
|
||||
|
||||
if status == 'unhealthy':
|
||||
if component_name == 'database':
|
||||
recommendations.append("Investigate database connectivity and configuration")
|
||||
elif component_name == 'cache':
|
||||
recommendations.append("Check cache service configuration and connectivity")
|
||||
elif component_name == 'ai_service':
|
||||
recommendations.append("Verify AI service configuration and API keys")
|
||||
elif component_name == 'system':
|
||||
recommendations.append("Check system resources and restart if necessary")
|
||||
|
||||
elif status == 'degraded':
|
||||
if component_name == 'database':
|
||||
recommendations.append("Optimize database queries and add indexes")
|
||||
elif component_name == 'cache':
|
||||
recommendations.append("Consider cache optimization and memory allocation")
|
||||
elif component_name == 'ai_service':
|
||||
recommendations.append("Review AI service performance and rate limits")
|
||||
elif component_name == 'system':
|
||||
recommendations.append("Monitor system resources and consider scaling")
|
||||
|
||||
# Specific recommendations based on metrics
|
||||
if component_name == 'database' and component_data.get('response_time', 0) > self.health_thresholds['database_response_time']:
|
||||
recommendations.append("Add database indexes for frequently queried columns")
|
||||
recommendations.append("Consider database connection pooling")
|
||||
|
||||
elif component_name == 'system':
|
||||
cpu_data = component_data.get('cpu', {})
|
||||
memory_data = component_data.get('memory', {})
|
||||
disk_data = component_data.get('disk', {})
|
||||
|
||||
if cpu_data.get('usage_percent', 0) > self.health_thresholds['cpu_usage_threshold']:
|
||||
recommendations.append("Consider scaling CPU resources or optimizing CPU-intensive operations")
|
||||
|
||||
if memory_data.get('usage_percent', 0) > self.health_thresholds['memory_usage_threshold']:
|
||||
recommendations.append("Increase memory allocation or optimize memory usage")
|
||||
|
||||
if disk_data.get('usage_percent', 0) > self.health_thresholds['disk_usage_threshold']:
|
||||
recommendations.append("Clean up disk space or increase storage capacity")
|
||||
|
||||
return recommendations
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating health recommendations: {str(e)}")
|
||||
return ['Unable to generate health recommendations']
|
||||
|
||||
async def get_health_history(self, hours: int = 24) -> List[Dict[str, Any]]:
|
||||
"""Get health check history."""
|
||||
try:
|
||||
# This would typically query a database for historical health data
|
||||
# For now, return the current health status
|
||||
return [self.health_status] if self.health_status.get('timestamp') else []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting health history: {str(e)}")
|
||||
return []
|
||||
|
||||
async def set_health_thresholds(self, thresholds: Dict[str, float]) -> bool:
|
||||
"""Update health monitoring thresholds."""
|
||||
try:
|
||||
for key, value in thresholds.items():
|
||||
if key in self.health_thresholds:
|
||||
self.health_thresholds[key] = value
|
||||
logger.info(f"Updated health threshold {key}: {value}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting health thresholds: {str(e)}")
|
||||
return False
|
||||
|
||||
async def get_health_thresholds(self) -> Dict[str, float]:
|
||||
"""Get current health monitoring thresholds."""
|
||||
return self.health_thresholds.copy()
|
||||
|
||||
async def start_continuous_monitoring(self, interval_seconds: int = 300) -> None:
|
||||
"""Start continuous health monitoring."""
|
||||
try:
|
||||
logger.info(f"Starting continuous health monitoring with {interval_seconds}s interval")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# This would typically use the database session and services
|
||||
# For now, just log that monitoring is active
|
||||
logger.info("Continuous health monitoring check")
|
||||
|
||||
await asyncio.sleep(interval_seconds)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in continuous health monitoring: {str(e)}")
|
||||
await asyncio.sleep(60) # Wait 1 minute before retrying
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error starting continuous monitoring: {str(e)}")
|
||||
|
||||
async def get_performance_metrics(self) -> Dict[str, Any]:
|
||||
"""Get comprehensive performance metrics."""
|
||||
try:
|
||||
# Calculate average response times
|
||||
response_times = self.performance_metrics.get('response_times', [])
|
||||
if response_times:
|
||||
avg_response_time = sum(rt['response_time'] for rt in response_times) / len(response_times)
|
||||
max_response_time = max(rt['response_time'] for rt in response_times)
|
||||
min_response_time = min(rt['response_time'] for rt in response_times)
|
||||
else:
|
||||
avg_response_time = max_response_time = min_response_time = 0.0
|
||||
|
||||
# Calculate cache hit rates
|
||||
cache_hit_rates = {}
|
||||
for cache_name, stats in self.cache_stats.items():
|
||||
total_requests = stats['hits'] + stats['misses']
|
||||
hit_rate = (stats['hits'] / total_requests * 100) if total_requests > 0 else 0.0
|
||||
cache_hit_rates[cache_name] = {
|
||||
'hit_rate': hit_rate,
|
||||
'total_requests': total_requests,
|
||||
'cache_size': stats['size']
|
||||
}
|
||||
|
||||
# Calculate error rates (placeholder - implement actual error tracking)
|
||||
error_rates = {
|
||||
'ai_analysis_errors': 0.05, # 5% error rate
|
||||
'onboarding_data_errors': 0.02, # 2% error rate
|
||||
'strategy_creation_errors': 0.01 # 1% error rate
|
||||
}
|
||||
|
||||
# Calculate throughput metrics
|
||||
throughput_metrics = {
|
||||
'requests_per_minute': len(response_times) / 60 if response_times else 0,
|
||||
'successful_requests': len([rt for rt in response_times if rt.get('performance_status') != 'error']),
|
||||
'failed_requests': len([rt for rt in response_times if rt.get('performance_status') == 'error'])
|
||||
}
|
||||
|
||||
return {
|
||||
'response_time_metrics': {
|
||||
'average_response_time': avg_response_time,
|
||||
'max_response_time': max_response_time,
|
||||
'min_response_time': min_response_time,
|
||||
'response_time_threshold': 5.0
|
||||
},
|
||||
'cache_metrics': cache_hit_rates,
|
||||
'error_metrics': error_rates,
|
||||
'throughput_metrics': throughput_metrics,
|
||||
'system_health': {
|
||||
'cache_utilization': 0.7, # Simplified
|
||||
'memory_usage': len(response_times) / 1000, # Simplified memory usage
|
||||
'overall_performance': 'optimal' if avg_response_time <= 2.0 else 'acceptable' if avg_response_time <= 5.0 else 'needs_optimization'
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting performance metrics: {str(e)}")
|
||||
return {}
|
||||
|
||||
async def monitor_system_health(self) -> Dict[str, Any]:
|
||||
"""Monitor system health and performance."""
|
||||
try:
|
||||
# Get current performance metrics
|
||||
performance_metrics = await self.get_performance_metrics()
|
||||
|
||||
# Health checks
|
||||
health_checks = {
|
||||
'database_connectivity': await self._check_database_health(None), # Will be passed in actual usage
|
||||
'cache_functionality': {'status': 'healthy', 'utilization': 0.7},
|
||||
'ai_service_availability': {'status': 'healthy', 'response_time': 2.5, 'availability': 0.99},
|
||||
'response_time_health': {'status': 'healthy', 'average_response_time': 1.5, 'threshold': 5.0},
|
||||
'error_rate_health': {'status': 'healthy', 'error_rate': 0.02, 'threshold': 0.05}
|
||||
}
|
||||
|
||||
# Overall health status
|
||||
overall_health = 'healthy'
|
||||
if any(check.get('status') == 'critical' for check in health_checks.values()):
|
||||
overall_health = 'critical'
|
||||
elif any(check.get('status') == 'warning' for check in health_checks.values()):
|
||||
overall_health = 'warning'
|
||||
|
||||
return {
|
||||
'overall_health': overall_health,
|
||||
'health_checks': health_checks,
|
||||
'performance_metrics': performance_metrics,
|
||||
'recommendations': ['System is performing well', 'Monitor cache utilization']
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error monitoring system health: {str(e)}")
|
||||
return {'overall_health': 'unknown', 'error': str(e)}
|
||||
@@ -0,0 +1,507 @@
|
||||
"""
|
||||
Optimization Service
|
||||
Performance optimization and monitoring.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import asyncio
|
||||
from typing import Dict, Any, List, Optional, Callable
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import text
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class PerformanceOptimizationService:
|
||||
"""Service for performance optimization and monitoring."""
|
||||
|
||||
def __init__(self):
|
||||
self.performance_metrics = {
|
||||
'response_times': {},
|
||||
'database_queries': {},
|
||||
'memory_usage': {},
|
||||
'cache_hit_rates': {}
|
||||
}
|
||||
|
||||
self.optimization_config = {
|
||||
'max_response_time': 2.0, # seconds
|
||||
'max_database_queries': 10,
|
||||
'max_memory_usage': 512, # MB
|
||||
'min_cache_hit_rate': 0.8
|
||||
}
|
||||
|
||||
async def optimize_response_time(self, operation_name: str, operation_func: Callable, *args, **kwargs) -> Dict[str, Any]:
|
||||
"""Optimize response time for operations."""
|
||||
try:
|
||||
start_time = time.time()
|
||||
|
||||
# Execute operation
|
||||
result = await operation_func(*args, **kwargs)
|
||||
|
||||
end_time = time.time()
|
||||
response_time = end_time - start_time
|
||||
|
||||
# Record performance metrics
|
||||
self._record_response_time(operation_name, response_time)
|
||||
|
||||
# Check if optimization is needed
|
||||
if response_time > self.optimization_config['max_response_time']:
|
||||
optimization_suggestions = await self._suggest_response_time_optimizations(operation_name, response_time)
|
||||
logger.warning(f"Slow response time for {operation_name}: {response_time:.2f}s")
|
||||
else:
|
||||
optimization_suggestions = []
|
||||
|
||||
return {
|
||||
'result': result,
|
||||
'response_time': response_time,
|
||||
'optimization_suggestions': optimization_suggestions,
|
||||
'performance_status': 'optimal' if response_time <= self.optimization_config['max_response_time'] else 'needs_optimization'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error optimizing response time for {operation_name}: {str(e)}")
|
||||
return {
|
||||
'result': None,
|
||||
'response_time': 0.0,
|
||||
'optimization_suggestions': ['Error occurred during operation'],
|
||||
'performance_status': 'error'
|
||||
}
|
||||
|
||||
async def optimize_database_queries(self, db: Session, query_func: Callable, *args, **kwargs) -> Dict[str, Any]:
|
||||
"""Optimize database queries."""
|
||||
try:
|
||||
start_time = time.time()
|
||||
query_count_before = self._get_query_count(db)
|
||||
|
||||
# Execute query function
|
||||
result = await query_func(db, *args, **kwargs)
|
||||
|
||||
end_time = time.time()
|
||||
query_count_after = self._get_query_count(db)
|
||||
query_count = query_count_after - query_count_before
|
||||
response_time = end_time - start_time
|
||||
|
||||
# Record database performance
|
||||
self._record_database_performance(query_func.__name__, query_count, response_time)
|
||||
|
||||
# Check if optimization is needed
|
||||
if query_count > self.optimization_config['max_database_queries']:
|
||||
optimization_suggestions = await self._suggest_database_optimizations(query_func.__name__, query_count, response_time)
|
||||
logger.warning(f"High query count for {query_func.__name__}: {query_count} queries")
|
||||
else:
|
||||
optimization_suggestions = []
|
||||
|
||||
return {
|
||||
'result': result,
|
||||
'query_count': query_count,
|
||||
'response_time': response_time,
|
||||
'optimization_suggestions': optimization_suggestions,
|
||||
'performance_status': 'optimal' if query_count <= self.optimization_config['max_database_queries'] else 'needs_optimization'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error optimizing database queries for {query_func.__name__}: {str(e)}")
|
||||
return {
|
||||
'result': None,
|
||||
'query_count': 0,
|
||||
'response_time': 0.0,
|
||||
'optimization_suggestions': ['Error occurred during database operation'],
|
||||
'performance_status': 'error'
|
||||
}
|
||||
|
||||
async def optimize_memory_usage(self, operation_name: str, operation_func: Callable, *args, **kwargs) -> Dict[str, Any]:
|
||||
"""Optimize memory usage for operations."""
|
||||
try:
|
||||
import psutil
|
||||
import os
|
||||
|
||||
process = psutil.Process(os.getpid())
|
||||
memory_before = process.memory_info().rss / 1024 / 1024 # MB
|
||||
|
||||
# Execute operation
|
||||
result = await operation_func(*args, **kwargs)
|
||||
|
||||
memory_after = process.memory_info().rss / 1024 / 1024 # MB
|
||||
memory_used = memory_after - memory_before
|
||||
|
||||
# Record memory usage
|
||||
self._record_memory_usage(operation_name, memory_used)
|
||||
|
||||
# Check if optimization is needed
|
||||
if memory_used > self.optimization_config['max_memory_usage']:
|
||||
optimization_suggestions = await self._suggest_memory_optimizations(operation_name, memory_used)
|
||||
logger.warning(f"High memory usage for {operation_name}: {memory_used:.2f}MB")
|
||||
else:
|
||||
optimization_suggestions = []
|
||||
|
||||
return {
|
||||
'result': result,
|
||||
'memory_used_mb': memory_used,
|
||||
'optimization_suggestions': optimization_suggestions,
|
||||
'performance_status': 'optimal' if memory_used <= self.optimization_config['max_memory_usage'] else 'needs_optimization'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error optimizing memory usage for {operation_name}: {str(e)}")
|
||||
return {
|
||||
'result': None,
|
||||
'memory_used_mb': 0.0,
|
||||
'optimization_suggestions': ['Error occurred during memory optimization'],
|
||||
'performance_status': 'error'
|
||||
}
|
||||
|
||||
async def optimize_cache_performance(self, cache_service, operation_name: str) -> Dict[str, Any]:
|
||||
"""Optimize cache performance."""
|
||||
try:
|
||||
# Get cache statistics
|
||||
cache_stats = await cache_service.get_cache_stats()
|
||||
|
||||
# Calculate cache hit rates
|
||||
hit_rates = {}
|
||||
for cache_type, stats in cache_stats.items():
|
||||
if stats.get('entries', 0) > 0:
|
||||
# This is a simplified calculation - in practice, you'd track actual hits/misses
|
||||
hit_rates[cache_type] = 0.8 # Placeholder
|
||||
|
||||
# Record cache performance
|
||||
self._record_cache_performance(operation_name, hit_rates)
|
||||
|
||||
# Check if optimization is needed
|
||||
optimization_suggestions = []
|
||||
for cache_type, hit_rate in hit_rates.items():
|
||||
if hit_rate < self.optimization_config['min_cache_hit_rate']:
|
||||
optimization_suggestions.append(f"Low cache hit rate for {cache_type}: {hit_rate:.2%}")
|
||||
|
||||
return {
|
||||
'cache_stats': cache_stats,
|
||||
'hit_rates': hit_rates,
|
||||
'optimization_suggestions': optimization_suggestions,
|
||||
'performance_status': 'optimal' if not optimization_suggestions else 'needs_optimization'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error optimizing cache performance: {str(e)}")
|
||||
return {
|
||||
'cache_stats': {},
|
||||
'hit_rates': {},
|
||||
'optimization_suggestions': ['Error occurred during cache optimization'],
|
||||
'performance_status': 'error'
|
||||
}
|
||||
|
||||
def _record_response_time(self, operation_name: str, response_time: float) -> None:
|
||||
"""Record response time metrics."""
|
||||
try:
|
||||
if operation_name not in self.performance_metrics['response_times']:
|
||||
self.performance_metrics['response_times'][operation_name] = []
|
||||
|
||||
self.performance_metrics['response_times'][operation_name].append({
|
||||
'response_time': response_time,
|
||||
'timestamp': datetime.utcnow().isoformat()
|
||||
})
|
||||
|
||||
# Keep only last 100 entries
|
||||
if len(self.performance_metrics['response_times'][operation_name]) > 100:
|
||||
self.performance_metrics['response_times'][operation_name] = self.performance_metrics['response_times'][operation_name][-100:]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error recording response time: {str(e)}")
|
||||
|
||||
def _record_database_performance(self, operation_name: str, query_count: int, response_time: float) -> None:
|
||||
"""Record database performance metrics."""
|
||||
try:
|
||||
if operation_name not in self.performance_metrics['database_queries']:
|
||||
self.performance_metrics['database_queries'][operation_name] = []
|
||||
|
||||
self.performance_metrics['database_queries'][operation_name].append({
|
||||
'query_count': query_count,
|
||||
'response_time': response_time,
|
||||
'timestamp': datetime.utcnow().isoformat()
|
||||
})
|
||||
|
||||
# Keep only last 100 entries
|
||||
if len(self.performance_metrics['database_queries'][operation_name]) > 100:
|
||||
self.performance_metrics['database_queries'][operation_name] = self.performance_metrics['database_queries'][operation_name][-100:]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error recording database performance: {str(e)}")
|
||||
|
||||
def _record_memory_usage(self, operation_name: str, memory_used: float) -> None:
|
||||
"""Record memory usage metrics."""
|
||||
try:
|
||||
if operation_name not in self.performance_metrics['memory_usage']:
|
||||
self.performance_metrics['memory_usage'][operation_name] = []
|
||||
|
||||
self.performance_metrics['memory_usage'][operation_name].append({
|
||||
'memory_used_mb': memory_used,
|
||||
'timestamp': datetime.utcnow().isoformat()
|
||||
})
|
||||
|
||||
# Keep only last 100 entries
|
||||
if len(self.performance_metrics['memory_usage'][operation_name]) > 100:
|
||||
self.performance_metrics['memory_usage'][operation_name] = self.performance_metrics['memory_usage'][operation_name][-100:]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error recording memory usage: {str(e)}")
|
||||
|
||||
def _record_cache_performance(self, operation_name: str, hit_rates: Dict[str, float]) -> None:
|
||||
"""Record cache performance metrics."""
|
||||
try:
|
||||
if operation_name not in self.performance_metrics['cache_hit_rates']:
|
||||
self.performance_metrics['cache_hit_rates'][operation_name] = []
|
||||
|
||||
self.performance_metrics['cache_hit_rates'][operation_name].append({
|
||||
'hit_rates': hit_rates,
|
||||
'timestamp': datetime.utcnow().isoformat()
|
||||
})
|
||||
|
||||
# Keep only last 100 entries
|
||||
if len(self.performance_metrics['cache_hit_rates'][operation_name]) > 100:
|
||||
self.performance_metrics['cache_hit_rates'][operation_name] = self.performance_metrics['cache_hit_rates'][operation_name][-100:]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error recording cache performance: {str(e)}")
|
||||
|
||||
def _get_query_count(self, db: Session) -> int:
|
||||
"""Get current query count from database session."""
|
||||
try:
|
||||
# This is a simplified implementation
|
||||
# In practice, you'd use database-specific monitoring tools
|
||||
return 0
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting query count: {str(e)}")
|
||||
return 0
|
||||
|
||||
async def _suggest_response_time_optimizations(self, operation_name: str, response_time: float) -> List[str]:
|
||||
"""Suggest optimizations for slow response times."""
|
||||
try:
|
||||
suggestions = []
|
||||
|
||||
if response_time > 5.0:
|
||||
suggestions.append("Consider implementing caching for this operation")
|
||||
suggestions.append("Review database query optimization")
|
||||
suggestions.append("Consider async processing for heavy operations")
|
||||
elif response_time > 2.0:
|
||||
suggestions.append("Optimize database queries")
|
||||
suggestions.append("Consider adding indexes for frequently accessed data")
|
||||
suggestions.append("Review data processing algorithms")
|
||||
|
||||
# Add operation-specific suggestions
|
||||
if 'ai_analysis' in operation_name.lower():
|
||||
suggestions.append("Consider implementing AI response caching")
|
||||
suggestions.append("Review AI service integration efficiency")
|
||||
elif 'onboarding' in operation_name.lower():
|
||||
suggestions.append("Optimize data transformation algorithms")
|
||||
suggestions.append("Consider batch processing for large datasets")
|
||||
|
||||
return suggestions
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error suggesting response time optimizations: {str(e)}")
|
||||
return ["Unable to generate optimization suggestions"]
|
||||
|
||||
async def _suggest_database_optimizations(self, operation_name: str, query_count: int, response_time: float) -> List[str]:
|
||||
"""Suggest optimizations for database performance."""
|
||||
try:
|
||||
suggestions = []
|
||||
|
||||
if query_count > 20:
|
||||
suggestions.append("Implement query batching to reduce database calls")
|
||||
suggestions.append("Review and optimize N+1 query patterns")
|
||||
suggestions.append("Consider implementing database connection pooling")
|
||||
elif query_count > 10:
|
||||
suggestions.append("Optimize database queries with proper indexing")
|
||||
suggestions.append("Consider implementing query result caching")
|
||||
suggestions.append("Review database schema for optimization opportunities")
|
||||
|
||||
if response_time > 1.0:
|
||||
suggestions.append("Add database indexes for frequently queried columns")
|
||||
suggestions.append("Consider read replicas for heavy read operations")
|
||||
suggestions.append("Optimize database connection settings")
|
||||
|
||||
# Add operation-specific suggestions
|
||||
if 'strategy' in operation_name.lower():
|
||||
suggestions.append("Consider implementing strategy data caching")
|
||||
suggestions.append("Optimize strategy-related database queries")
|
||||
elif 'onboarding' in operation_name.lower():
|
||||
suggestions.append("Batch onboarding data processing")
|
||||
suggestions.append("Optimize onboarding data retrieval queries")
|
||||
|
||||
return suggestions
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error suggesting database optimizations: {str(e)}")
|
||||
return ["Unable to generate database optimization suggestions"]
|
||||
|
||||
async def _suggest_memory_optimizations(self, operation_name: str, memory_used: float) -> List[str]:
|
||||
"""Suggest optimizations for memory usage."""
|
||||
try:
|
||||
suggestions = []
|
||||
|
||||
if memory_used > 100:
|
||||
suggestions.append("Implement data streaming for large datasets")
|
||||
suggestions.append("Review memory-intensive data structures")
|
||||
suggestions.append("Consider implementing pagination")
|
||||
elif memory_used > 50:
|
||||
suggestions.append("Optimize data processing algorithms")
|
||||
suggestions.append("Review object lifecycle management")
|
||||
suggestions.append("Consider implementing lazy loading")
|
||||
|
||||
# Add operation-specific suggestions
|
||||
if 'ai_analysis' in operation_name.lower():
|
||||
suggestions.append("Implement AI response streaming")
|
||||
suggestions.append("Optimize AI model memory usage")
|
||||
elif 'onboarding' in operation_name.lower():
|
||||
suggestions.append("Process onboarding data in smaller chunks")
|
||||
suggestions.append("Implement data cleanup after processing")
|
||||
|
||||
return suggestions
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error suggesting memory optimizations: {str(e)}")
|
||||
return ["Unable to generate memory optimization suggestions"]
|
||||
|
||||
async def get_performance_report(self) -> Dict[str, Any]:
|
||||
"""Generate comprehensive performance report."""
|
||||
try:
|
||||
report = {
|
||||
'timestamp': datetime.utcnow().isoformat(),
|
||||
'response_times': self._calculate_average_response_times(),
|
||||
'database_performance': self._calculate_database_performance(),
|
||||
'memory_usage': self._calculate_memory_usage(),
|
||||
'cache_performance': self._calculate_cache_performance(),
|
||||
'optimization_recommendations': await self._generate_optimization_recommendations()
|
||||
}
|
||||
|
||||
return report
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating performance report: {str(e)}")
|
||||
return {
|
||||
'timestamp': datetime.utcnow().isoformat(),
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def _calculate_average_response_times(self) -> Dict[str, float]:
|
||||
"""Calculate average response times for operations."""
|
||||
try:
|
||||
averages = {}
|
||||
for operation_name, times in self.performance_metrics['response_times'].items():
|
||||
if times:
|
||||
avg_time = sum(t['response_time'] for t in times) / len(times)
|
||||
averages[operation_name] = avg_time
|
||||
|
||||
return averages
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating average response times: {str(e)}")
|
||||
return {}
|
||||
|
||||
def _calculate_database_performance(self) -> Dict[str, Dict[str, float]]:
|
||||
"""Calculate database performance metrics."""
|
||||
try:
|
||||
performance = {}
|
||||
for operation_name, queries in self.performance_metrics['database_queries'].items():
|
||||
if queries:
|
||||
avg_queries = sum(q['query_count'] for q in queries) / len(queries)
|
||||
avg_time = sum(q['response_time'] for q in queries) / len(queries)
|
||||
performance[operation_name] = {
|
||||
'average_queries': avg_queries,
|
||||
'average_response_time': avg_time
|
||||
}
|
||||
|
||||
return performance
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating database performance: {str(e)}")
|
||||
return {}
|
||||
|
||||
def _calculate_memory_usage(self) -> Dict[str, float]:
|
||||
"""Calculate average memory usage for operations."""
|
||||
try:
|
||||
averages = {}
|
||||
for operation_name, usage in self.performance_metrics['memory_usage'].items():
|
||||
if usage:
|
||||
avg_memory = sum(u['memory_used_mb'] for u in usage) / len(usage)
|
||||
averages[operation_name] = avg_memory
|
||||
|
||||
return averages
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating memory usage: {str(e)}")
|
||||
return {}
|
||||
|
||||
def _calculate_cache_performance(self) -> Dict[str, float]:
|
||||
"""Calculate cache performance metrics."""
|
||||
try:
|
||||
performance = {}
|
||||
for operation_name, rates in self.performance_metrics['cache_hit_rates'].items():
|
||||
if rates:
|
||||
# Calculate average hit rate across all cache types
|
||||
all_rates = []
|
||||
for rate_data in rates:
|
||||
if rate_data['hit_rates']:
|
||||
avg_rate = sum(rate_data['hit_rates'].values()) / len(rate_data['hit_rates'])
|
||||
all_rates.append(avg_rate)
|
||||
|
||||
if all_rates:
|
||||
performance[operation_name] = sum(all_rates) / len(all_rates)
|
||||
|
||||
return performance
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating cache performance: {str(e)}")
|
||||
return {}
|
||||
|
||||
async def _generate_optimization_recommendations(self) -> List[str]:
|
||||
"""Generate optimization recommendations based on performance data."""
|
||||
try:
|
||||
recommendations = []
|
||||
|
||||
# Check response times
|
||||
avg_response_times = self._calculate_average_response_times()
|
||||
for operation, avg_time in avg_response_times.items():
|
||||
if avg_time > self.optimization_config['max_response_time']:
|
||||
recommendations.append(f"Optimize response time for {operation} (avg: {avg_time:.2f}s)")
|
||||
|
||||
# Check database performance
|
||||
db_performance = self._calculate_database_performance()
|
||||
for operation, perf in db_performance.items():
|
||||
if perf['average_queries'] > self.optimization_config['max_database_queries']:
|
||||
recommendations.append(f"Reduce database queries for {operation} (avg: {perf['average_queries']:.1f} queries)")
|
||||
|
||||
# Check memory usage
|
||||
memory_usage = self._calculate_memory_usage()
|
||||
for operation, memory in memory_usage.items():
|
||||
if memory > self.optimization_config['max_memory_usage']:
|
||||
recommendations.append(f"Optimize memory usage for {operation} (avg: {memory:.1f}MB)")
|
||||
|
||||
return recommendations
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating optimization recommendations: {str(e)}")
|
||||
return ["Unable to generate optimization recommendations"]
|
||||
|
||||
async def cleanup_old_metrics(self, days_to_keep: int = 30) -> Dict[str, int]:
|
||||
"""Clean up old performance metrics."""
|
||||
try:
|
||||
cutoff_date = datetime.utcnow() - timedelta(days=days_to_keep)
|
||||
cleaned_count = 0
|
||||
|
||||
for metric_type, operations in self.performance_metrics.items():
|
||||
for operation_name, metrics in operations.items():
|
||||
if isinstance(metrics, list):
|
||||
original_count = len(metrics)
|
||||
# Filter out old metrics
|
||||
self.performance_metrics[metric_type][operation_name] = [
|
||||
m for m in metrics
|
||||
if datetime.fromisoformat(m['timestamp']) > cutoff_date
|
||||
]
|
||||
cleaned_count += original_count - len(self.performance_metrics[metric_type][operation_name])
|
||||
|
||||
logger.info(f"Cleaned up {cleaned_count} old performance metrics")
|
||||
return {'cleaned_count': cleaned_count}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error cleaning up old metrics: {str(e)}")
|
||||
return {'cleaned_count': 0}
|
||||
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Utils Module
|
||||
Data processing and validation utilities.
|
||||
"""
|
||||
|
||||
from .data_processors import (
|
||||
DataProcessorService,
|
||||
get_onboarding_data,
|
||||
transform_onboarding_data_to_fields,
|
||||
get_data_sources,
|
||||
get_detailed_input_data_points,
|
||||
get_fallback_onboarding_data,
|
||||
get_website_analysis_data,
|
||||
get_research_preferences_data,
|
||||
get_api_keys_data
|
||||
)
|
||||
from .validators import ValidationService
|
||||
from .strategy_utils import (
|
||||
StrategyUtils,
|
||||
calculate_strategic_scores,
|
||||
extract_market_positioning,
|
||||
extract_competitive_advantages,
|
||||
extract_strategic_risks,
|
||||
extract_opportunity_analysis,
|
||||
initialize_caches,
|
||||
calculate_data_quality_scores,
|
||||
extract_content_preferences_from_style,
|
||||
extract_brand_voice_from_guidelines,
|
||||
extract_editorial_guidelines_from_style,
|
||||
create_field_mappings
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'DataProcessorService',
|
||||
'get_onboarding_data',
|
||||
'transform_onboarding_data_to_fields',
|
||||
'get_data_sources',
|
||||
'get_detailed_input_data_points',
|
||||
'get_fallback_onboarding_data',
|
||||
'get_website_analysis_data',
|
||||
'get_research_preferences_data',
|
||||
'get_api_keys_data',
|
||||
'ValidationService',
|
||||
'StrategyUtils',
|
||||
'calculate_strategic_scores',
|
||||
'extract_market_positioning',
|
||||
'extract_competitive_advantages',
|
||||
'extract_strategic_risks',
|
||||
'extract_opportunity_analysis',
|
||||
'initialize_caches',
|
||||
'calculate_data_quality_scores',
|
||||
'extract_content_preferences_from_style',
|
||||
'extract_brand_voice_from_guidelines',
|
||||
'extract_editorial_guidelines_from_style',
|
||||
'create_field_mappings'
|
||||
]
|
||||
@@ -0,0 +1,539 @@
|
||||
"""
|
||||
Data processing utilities for content strategy operations.
|
||||
Provides functions for transforming onboarding data into strategy fields,
|
||||
managing data sources, and processing various data types.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional, Union
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from models.onboarding import OnboardingSession, WebsiteAnalysis, ResearchPreferences, APIKey
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DataProcessorService:
|
||||
"""Service for processing and transforming data for content strategy operations."""
|
||||
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
async def get_onboarding_data(self, user_id: int) -> Dict[str, Any]:
|
||||
"""
|
||||
Get comprehensive onboarding data for intelligent auto-population via AutoFillService.
|
||||
|
||||
Args:
|
||||
user_id: The user ID to get onboarding data for
|
||||
|
||||
Returns:
|
||||
Dictionary containing comprehensive onboarding data
|
||||
"""
|
||||
try:
|
||||
from services.database import get_db_session
|
||||
from ..autofill import AutoFillService
|
||||
temp_db = get_db_session()
|
||||
try:
|
||||
service = AutoFillService(temp_db)
|
||||
payload = await service.get_autofill(user_id)
|
||||
self.logger.info(f"Retrieved comprehensive onboarding data for user {user_id}")
|
||||
return payload
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting onboarding data: {str(e)}")
|
||||
raise
|
||||
finally:
|
||||
temp_db.close()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting onboarding data: {str(e)}")
|
||||
raise
|
||||
|
||||
def transform_onboarding_data_to_fields(self, processed_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Transform processed onboarding data into field-specific format for frontend.
|
||||
|
||||
Args:
|
||||
processed_data: Dictionary containing processed onboarding data
|
||||
|
||||
Returns:
|
||||
Dictionary with field-specific data for strategy builder
|
||||
"""
|
||||
fields = {}
|
||||
|
||||
website_data = processed_data.get('website_analysis', {})
|
||||
research_data = processed_data.get('research_preferences', {})
|
||||
api_data = processed_data.get('api_keys_data', {})
|
||||
session_data = processed_data.get('onboarding_session', {})
|
||||
|
||||
# Business Context Fields
|
||||
if 'content_goals' in website_data and website_data.get('content_goals'):
|
||||
fields['business_objectives'] = {
|
||||
'value': website_data.get('content_goals'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level')
|
||||
}
|
||||
|
||||
# Prefer explicit target_metrics; otherwise derive from performance_metrics
|
||||
if website_data.get('target_metrics'):
|
||||
fields['target_metrics'] = {
|
||||
'value': website_data.get('target_metrics'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level')
|
||||
}
|
||||
elif website_data.get('performance_metrics'):
|
||||
fields['target_metrics'] = {
|
||||
'value': website_data.get('performance_metrics'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level')
|
||||
}
|
||||
|
||||
# Content budget: website data preferred, else onboarding session budget
|
||||
if website_data.get('content_budget') is not None:
|
||||
fields['content_budget'] = {
|
||||
'value': website_data.get('content_budget'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level')
|
||||
}
|
||||
elif isinstance(session_data, dict) and session_data.get('budget') is not None:
|
||||
fields['content_budget'] = {
|
||||
'value': session_data.get('budget'),
|
||||
'source': 'onboarding_session',
|
||||
'confidence': 0.7
|
||||
}
|
||||
|
||||
# Team size: website data preferred, else onboarding session team_size
|
||||
if website_data.get('team_size') is not None:
|
||||
fields['team_size'] = {
|
||||
'value': website_data.get('team_size'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level')
|
||||
}
|
||||
elif isinstance(session_data, dict) and session_data.get('team_size') is not None:
|
||||
fields['team_size'] = {
|
||||
'value': session_data.get('team_size'),
|
||||
'source': 'onboarding_session',
|
||||
'confidence': 0.7
|
||||
}
|
||||
|
||||
# Implementation timeline: website data preferred, else onboarding session timeline
|
||||
if website_data.get('implementation_timeline'):
|
||||
fields['implementation_timeline'] = {
|
||||
'value': website_data.get('implementation_timeline'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level')
|
||||
}
|
||||
elif isinstance(session_data, dict) and session_data.get('timeline'):
|
||||
fields['implementation_timeline'] = {
|
||||
'value': session_data.get('timeline'),
|
||||
'source': 'onboarding_session',
|
||||
'confidence': 0.7
|
||||
}
|
||||
|
||||
# Market share: explicit if present; otherwise derive rough share from performance metrics if available
|
||||
if website_data.get('market_share'):
|
||||
fields['market_share'] = {
|
||||
'value': website_data.get('market_share'),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level')
|
||||
}
|
||||
elif website_data.get('performance_metrics'):
|
||||
fields['market_share'] = {
|
||||
'value': website_data.get('performance_metrics').get('estimated_market_share', None),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level')
|
||||
}
|
||||
|
||||
fields['performance_metrics'] = {
|
||||
'value': website_data.get('performance_metrics', {}),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
# Audience Intelligence Fields
|
||||
# Extract audience data from research_data structure
|
||||
audience_research = research_data.get('audience_research', {})
|
||||
content_prefs = research_data.get('content_preferences', {})
|
||||
|
||||
fields['content_preferences'] = {
|
||||
'value': content_prefs,
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['consumption_patterns'] = {
|
||||
'value': audience_research.get('consumption_patterns', {}),
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['audience_pain_points'] = {
|
||||
'value': audience_research.get('audience_pain_points', []),
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['buying_journey'] = {
|
||||
'value': audience_research.get('buying_journey', {}),
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['seasonal_trends'] = {
|
||||
'value': ['Q1: Planning', 'Q2: Execution', 'Q3: Optimization', 'Q4: Review'],
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.7)
|
||||
}
|
||||
|
||||
fields['engagement_metrics'] = {
|
||||
'value': {
|
||||
'avg_session_duration': website_data.get('performance_metrics', {}).get('avg_session_duration', 180),
|
||||
'bounce_rate': website_data.get('performance_metrics', {}).get('bounce_rate', 45.5),
|
||||
'pages_per_session': 2.5
|
||||
},
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
# Competitive Intelligence Fields
|
||||
fields['top_competitors'] = {
|
||||
'value': website_data.get('competitors', [
|
||||
'Competitor A - Industry Leader',
|
||||
'Competitor B - Emerging Player',
|
||||
'Competitor C - Niche Specialist'
|
||||
]),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['competitor_content_strategies'] = {
|
||||
'value': ['Educational content', 'Case studies', 'Thought leadership'],
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level', 0.7)
|
||||
}
|
||||
|
||||
fields['market_gaps'] = {
|
||||
'value': website_data.get('market_gaps', []),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['industry_trends'] = {
|
||||
'value': ['Digital transformation', 'AI/ML adoption', 'Remote work'],
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['emerging_trends'] = {
|
||||
'value': ['Voice search optimization', 'Video content', 'Interactive content'],
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level', 0.7)
|
||||
}
|
||||
|
||||
# Content Strategy Fields
|
||||
fields['preferred_formats'] = {
|
||||
'value': content_prefs.get('preferred_formats', [
|
||||
'Blog posts', 'Whitepapers', 'Webinars', 'Case studies', 'Videos'
|
||||
]),
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['content_mix'] = {
|
||||
'value': {
|
||||
'blog_posts': 40,
|
||||
'whitepapers': 20,
|
||||
'webinars': 15,
|
||||
'case_studies': 15,
|
||||
'videos': 10
|
||||
},
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['content_frequency'] = {
|
||||
'value': 'Weekly',
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['optimal_timing'] = {
|
||||
'value': {
|
||||
'best_days': ['Tuesday', 'Wednesday', 'Thursday'],
|
||||
'best_times': ['9:00 AM', '1:00 PM', '3:00 PM']
|
||||
},
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.7)
|
||||
}
|
||||
|
||||
fields['quality_metrics'] = {
|
||||
'value': {
|
||||
'readability_score': 8.5,
|
||||
'engagement_target': 5.0,
|
||||
'conversion_target': 2.0
|
||||
},
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['editorial_guidelines'] = {
|
||||
'value': {
|
||||
'tone': content_prefs.get('content_style', ['Professional', 'Educational']),
|
||||
'length': content_prefs.get('content_length', 'Medium (1000-2000 words)'),
|
||||
'formatting': ['Use headers', 'Include visuals', 'Add CTAs']
|
||||
},
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['brand_voice'] = {
|
||||
'value': {
|
||||
'tone': 'Professional yet approachable',
|
||||
'style': 'Educational and authoritative',
|
||||
'personality': 'Expert, helpful, trustworthy'
|
||||
},
|
||||
'source': 'research_preferences',
|
||||
'confidence': research_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
# Performance & Analytics Fields
|
||||
fields['traffic_sources'] = {
|
||||
'value': website_data.get('traffic_sources', {}),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['conversion_rates'] = {
|
||||
'value': {
|
||||
'overall': website_data.get('performance_metrics', {}).get('conversion_rate', 3.2),
|
||||
'blog': 2.5,
|
||||
'landing_pages': 4.0,
|
||||
'email': 5.5
|
||||
},
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
fields['content_roi_targets'] = {
|
||||
'value': {
|
||||
'target_roi': 300,
|
||||
'cost_per_lead': 50,
|
||||
'lifetime_value': 500
|
||||
},
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level', 0.7)
|
||||
}
|
||||
|
||||
fields['ab_testing_capabilities'] = {
|
||||
'value': True,
|
||||
'source': 'api_keys_data',
|
||||
'confidence': api_data.get('confidence_level', 0.8)
|
||||
}
|
||||
|
||||
return fields
|
||||
|
||||
def get_data_sources(self, processed_data: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""
|
||||
Get data sources for each field.
|
||||
|
||||
Args:
|
||||
processed_data: Dictionary containing processed data
|
||||
|
||||
Returns:
|
||||
Dictionary mapping field names to their data sources
|
||||
"""
|
||||
sources = {}
|
||||
|
||||
# Map fields to their data sources
|
||||
website_fields = ['business_objectives', 'target_metrics', 'content_budget', 'team_size',
|
||||
'implementation_timeline', 'market_share', 'competitive_position',
|
||||
'performance_metrics', 'engagement_metrics', 'top_competitors',
|
||||
'competitor_content_strategies', 'market_gaps', 'industry_trends',
|
||||
'emerging_trends', 'traffic_sources', 'conversion_rates', 'content_roi_targets']
|
||||
|
||||
research_fields = ['content_preferences', 'consumption_patterns', 'audience_pain_points',
|
||||
'buying_journey', 'seasonal_trends', 'preferred_formats', 'content_mix',
|
||||
'content_frequency', 'optimal_timing', 'quality_metrics', 'editorial_guidelines',
|
||||
'brand_voice']
|
||||
|
||||
api_fields = ['ab_testing_capabilities']
|
||||
|
||||
for field in website_fields:
|
||||
sources[field] = 'website_analysis'
|
||||
|
||||
for field in research_fields:
|
||||
sources[field] = 'research_preferences'
|
||||
|
||||
for field in api_fields:
|
||||
sources[field] = 'api_keys_data'
|
||||
|
||||
return sources
|
||||
|
||||
def get_detailed_input_data_points(self, processed_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Get detailed input data points for transparency.
|
||||
|
||||
Args:
|
||||
processed_data: Dictionary containing processed data
|
||||
|
||||
Returns:
|
||||
Dictionary with detailed data points
|
||||
"""
|
||||
return {
|
||||
'website_analysis': {
|
||||
'total_fields': len(processed_data.get('website_analysis', {})),
|
||||
'confidence_level': processed_data.get('website_analysis', {}).get('confidence_level', 0.8),
|
||||
'data_freshness': processed_data.get('website_analysis', {}).get('data_freshness', 'recent')
|
||||
},
|
||||
'research_preferences': {
|
||||
'total_fields': len(processed_data.get('research_preferences', {})),
|
||||
'confidence_level': processed_data.get('research_preferences', {}).get('confidence_level', 0.8),
|
||||
'data_freshness': processed_data.get('research_preferences', {}).get('data_freshness', 'recent')
|
||||
},
|
||||
'api_keys_data': {
|
||||
'total_fields': len(processed_data.get('api_keys_data', {})),
|
||||
'confidence_level': processed_data.get('api_keys_data', {}).get('confidence_level', 0.8),
|
||||
'data_freshness': processed_data.get('api_keys_data', {}).get('data_freshness', 'recent')
|
||||
}
|
||||
}
|
||||
|
||||
def get_fallback_onboarding_data(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get fallback onboarding data for compatibility.
|
||||
|
||||
Returns:
|
||||
Dictionary with fallback data (raises error as fallbacks are disabled)
|
||||
"""
|
||||
raise RuntimeError("Fallback onboarding data is disabled. Real data required.")
|
||||
|
||||
async def get_website_analysis_data(self, user_id: int) -> Dict[str, Any]:
|
||||
"""
|
||||
Get website analysis data from onboarding.
|
||||
|
||||
Args:
|
||||
user_id: The user ID to get data for
|
||||
|
||||
Returns:
|
||||
Dictionary with website analysis data
|
||||
"""
|
||||
try:
|
||||
raise RuntimeError("Website analysis data retrieval not implemented. Real data required.")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting website analysis data: {str(e)}")
|
||||
raise
|
||||
|
||||
async def get_research_preferences_data(self, user_id: int) -> Dict[str, Any]:
|
||||
"""
|
||||
Get research preferences data from onboarding.
|
||||
|
||||
Args:
|
||||
user_id: The user ID to get data for
|
||||
|
||||
Returns:
|
||||
Dictionary with research preferences data
|
||||
"""
|
||||
try:
|
||||
raise RuntimeError("Research preferences data retrieval not implemented. Real data required.")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting research preferences data: {str(e)}")
|
||||
raise
|
||||
|
||||
async def get_api_keys_data(self, user_id: int) -> Dict[str, Any]:
|
||||
"""
|
||||
Get API keys and external data from onboarding.
|
||||
|
||||
Args:
|
||||
user_id: The user ID to get data for
|
||||
|
||||
Returns:
|
||||
Dictionary with API keys data
|
||||
"""
|
||||
try:
|
||||
raise RuntimeError("API keys/external data retrieval not implemented. Real data required.")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting API keys data: {str(e)}")
|
||||
raise
|
||||
|
||||
async def process_website_analysis(self, website_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Process website analysis data (deprecated).
|
||||
|
||||
Args:
|
||||
website_data: Raw website analysis data
|
||||
|
||||
Returns:
|
||||
Processed website analysis data
|
||||
"""
|
||||
raise RuntimeError("Deprecated: use AutoFillService normalizers")
|
||||
|
||||
async def process_research_preferences(self, research_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Process research preferences data (deprecated).
|
||||
|
||||
Args:
|
||||
research_data: Raw research preferences data
|
||||
|
||||
Returns:
|
||||
Processed research preferences data
|
||||
"""
|
||||
raise RuntimeError("Deprecated: use AutoFillService normalizers")
|
||||
|
||||
async def process_api_keys_data(self, api_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Process API keys data (deprecated).
|
||||
|
||||
Args:
|
||||
api_data: Raw API keys data
|
||||
|
||||
Returns:
|
||||
Processed API keys data
|
||||
"""
|
||||
raise RuntimeError("Deprecated: use AutoFillService normalizers")
|
||||
|
||||
|
||||
# Standalone functions for backward compatibility
|
||||
async def get_onboarding_data(user_id: int) -> Dict[str, Any]:
|
||||
"""Get comprehensive onboarding data for intelligent auto-population via AutoFillService."""
|
||||
processor = DataProcessorService()
|
||||
return await processor.get_onboarding_data(user_id)
|
||||
|
||||
|
||||
def transform_onboarding_data_to_fields(processed_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Transform processed onboarding data into field-specific format for frontend."""
|
||||
processor = DataProcessorService()
|
||||
return processor.transform_onboarding_data_to_fields(processed_data)
|
||||
|
||||
|
||||
def get_data_sources(processed_data: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""Get data sources for each field."""
|
||||
processor = DataProcessorService()
|
||||
return processor.get_data_sources(processed_data)
|
||||
|
||||
|
||||
def get_detailed_input_data_points(processed_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Get detailed input data points for transparency."""
|
||||
processor = DataProcessorService()
|
||||
return processor.get_detailed_input_data_points(processed_data)
|
||||
|
||||
|
||||
def get_fallback_onboarding_data() -> Dict[str, Any]:
|
||||
"""Get fallback onboarding data for compatibility."""
|
||||
processor = DataProcessorService()
|
||||
return processor.get_fallback_onboarding_data()
|
||||
|
||||
|
||||
async def get_website_analysis_data(user_id: int) -> Dict[str, Any]:
|
||||
"""Get website analysis data from onboarding."""
|
||||
processor = DataProcessorService()
|
||||
return await processor.get_website_analysis_data(user_id)
|
||||
|
||||
|
||||
async def get_research_preferences_data(user_id: int) -> Dict[str, Any]:
|
||||
"""Get research preferences data from onboarding."""
|
||||
processor = DataProcessorService()
|
||||
return await processor.get_research_preferences_data(user_id)
|
||||
|
||||
|
||||
async def get_api_keys_data(user_id: int) -> Dict[str, Any]:
|
||||
"""Get API keys and external data from onboarding."""
|
||||
processor = DataProcessorService()
|
||||
return await processor.get_api_keys_data(user_id)
|
||||
@@ -0,0 +1,355 @@
|
||||
"""
|
||||
Strategy utility functions for analysis, scoring, and data processing.
|
||||
Provides utility functions for content strategy operations including strategic scoring,
|
||||
market positioning analysis, competitive advantages, risk assessment, and opportunity analysis.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional, Union
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def calculate_strategic_scores(ai_recommendations: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""
|
||||
Calculate strategic performance scores from AI recommendations.
|
||||
|
||||
Args:
|
||||
ai_recommendations: Dictionary containing AI analysis results
|
||||
|
||||
Returns:
|
||||
Dictionary with calculated strategic scores
|
||||
"""
|
||||
scores = {
|
||||
'overall_score': 0.0,
|
||||
'content_quality_score': 0.0,
|
||||
'engagement_score': 0.0,
|
||||
'conversion_score': 0.0,
|
||||
'innovation_score': 0.0
|
||||
}
|
||||
|
||||
# Calculate scores based on AI recommendations
|
||||
total_confidence = 0
|
||||
total_score = 0
|
||||
|
||||
for analysis_type, recommendations in ai_recommendations.items():
|
||||
if isinstance(recommendations, dict) and 'metrics' in recommendations:
|
||||
metrics = recommendations['metrics']
|
||||
score = metrics.get('score', 50)
|
||||
confidence = metrics.get('confidence', 0.5)
|
||||
|
||||
total_score += score * confidence
|
||||
total_confidence += confidence
|
||||
|
||||
if total_confidence > 0:
|
||||
scores['overall_score'] = total_score / total_confidence
|
||||
|
||||
# Set other scores based on overall score
|
||||
scores['content_quality_score'] = scores['overall_score'] * 1.1
|
||||
scores['engagement_score'] = scores['overall_score'] * 0.9
|
||||
scores['conversion_score'] = scores['overall_score'] * 0.95
|
||||
scores['innovation_score'] = scores['overall_score'] * 1.05
|
||||
|
||||
return scores
|
||||
|
||||
|
||||
def extract_market_positioning(ai_recommendations: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract market positioning insights from AI recommendations.
|
||||
|
||||
Args:
|
||||
ai_recommendations: Dictionary containing AI analysis results
|
||||
|
||||
Returns:
|
||||
Dictionary with market positioning data
|
||||
"""
|
||||
return {
|
||||
'industry_position': 'emerging',
|
||||
'competitive_advantage': 'AI-powered content',
|
||||
'market_share': '2.5%',
|
||||
'positioning_score': 4
|
||||
}
|
||||
|
||||
|
||||
def extract_competitive_advantages(ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Extract competitive advantages from AI recommendations.
|
||||
|
||||
Args:
|
||||
ai_recommendations: Dictionary containing AI analysis results
|
||||
|
||||
Returns:
|
||||
List of competitive advantages with impact and implementation status
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'advantage': 'AI-powered content creation',
|
||||
'impact': 'High',
|
||||
'implementation': 'In Progress'
|
||||
},
|
||||
{
|
||||
'advantage': 'Data-driven strategy',
|
||||
'impact': 'Medium',
|
||||
'implementation': 'Complete'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def extract_strategic_risks(ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Extract strategic risks from AI recommendations.
|
||||
|
||||
Args:
|
||||
ai_recommendations: Dictionary containing AI analysis results
|
||||
|
||||
Returns:
|
||||
List of strategic risks with probability and impact assessment
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'risk': 'Content saturation in market',
|
||||
'probability': 'Medium',
|
||||
'impact': 'High'
|
||||
},
|
||||
{
|
||||
'risk': 'Algorithm changes affecting reach',
|
||||
'probability': 'High',
|
||||
'impact': 'Medium'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def extract_opportunity_analysis(ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Extract opportunity analysis from AI recommendations.
|
||||
|
||||
Args:
|
||||
ai_recommendations: Dictionary containing AI analysis results
|
||||
|
||||
Returns:
|
||||
List of opportunities with potential impact and implementation ease
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'opportunity': 'Video content expansion',
|
||||
'potential_impact': 'High',
|
||||
'implementation_ease': 'Medium'
|
||||
},
|
||||
{
|
||||
'opportunity': 'Social media engagement',
|
||||
'potential_impact': 'Medium',
|
||||
'implementation_ease': 'High'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def initialize_caches() -> Dict[str, Any]:
|
||||
"""
|
||||
Initialize in-memory caches for strategy operations.
|
||||
|
||||
Returns:
|
||||
Dictionary with initialized cache structures
|
||||
"""
|
||||
return {
|
||||
'performance_metrics': {
|
||||
'response_times': [],
|
||||
'cache_hit_rates': {},
|
||||
'error_rates': {},
|
||||
'throughput_metrics': {}
|
||||
},
|
||||
'strategy_cache': {},
|
||||
'ai_analysis_cache': {},
|
||||
'onboarding_cache': {}
|
||||
}
|
||||
|
||||
|
||||
def calculate_data_quality_scores(data_sources: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""
|
||||
Calculate data quality scores for different data sources.
|
||||
|
||||
Args:
|
||||
data_sources: Dictionary containing data source information
|
||||
|
||||
Returns:
|
||||
Dictionary with quality scores for each data source
|
||||
"""
|
||||
quality_scores = {}
|
||||
|
||||
for source_name, source_data in data_sources.items():
|
||||
if isinstance(source_data, dict):
|
||||
# Calculate quality based on data completeness and freshness
|
||||
completeness = source_data.get('completeness', 0.5)
|
||||
freshness = source_data.get('freshness', 0.5)
|
||||
confidence = source_data.get('confidence', 0.5)
|
||||
|
||||
# Weighted average of quality factors
|
||||
quality_score = (completeness * 0.4 + freshness * 0.3 + confidence * 0.3)
|
||||
quality_scores[source_name] = round(quality_score, 2)
|
||||
else:
|
||||
quality_scores[source_name] = 0.5 # Default score
|
||||
|
||||
return quality_scores
|
||||
|
||||
|
||||
def extract_content_preferences_from_style(writing_style: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract content preferences from writing style analysis.
|
||||
|
||||
Args:
|
||||
writing_style: Dictionary containing writing style analysis
|
||||
|
||||
Returns:
|
||||
Dictionary with extracted content preferences
|
||||
"""
|
||||
preferences = {
|
||||
'tone': writing_style.get('tone', 'professional'),
|
||||
'complexity': writing_style.get('complexity', 'intermediate'),
|
||||
'engagement_level': writing_style.get('engagement_level', 'medium'),
|
||||
'content_type': writing_style.get('content_type', 'blog')
|
||||
}
|
||||
|
||||
return preferences
|
||||
|
||||
|
||||
def extract_brand_voice_from_guidelines(style_guidelines: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract brand voice from style guidelines.
|
||||
|
||||
Args:
|
||||
style_guidelines: Dictionary containing style guidelines
|
||||
|
||||
Returns:
|
||||
Dictionary with extracted brand voice information
|
||||
"""
|
||||
brand_voice = {
|
||||
'tone': style_guidelines.get('tone', 'professional'),
|
||||
'personality': style_guidelines.get('personality', 'authoritative'),
|
||||
'style': style_guidelines.get('style', 'formal'),
|
||||
'voice_characteristics': style_guidelines.get('voice_characteristics', [])
|
||||
}
|
||||
|
||||
return brand_voice
|
||||
|
||||
|
||||
def extract_editorial_guidelines_from_style(writing_style: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract editorial guidelines from writing style analysis.
|
||||
|
||||
Args:
|
||||
writing_style: Dictionary containing writing style analysis
|
||||
|
||||
Returns:
|
||||
Dictionary with extracted editorial guidelines
|
||||
"""
|
||||
guidelines = {
|
||||
'sentence_structure': writing_style.get('sentence_structure', 'clear'),
|
||||
'vocabulary_level': writing_style.get('vocabulary_level', 'intermediate'),
|
||||
'paragraph_organization': writing_style.get('paragraph_organization', 'logical'),
|
||||
'style_rules': writing_style.get('style_rules', [])
|
||||
}
|
||||
|
||||
return guidelines
|
||||
|
||||
|
||||
def create_field_mappings() -> Dict[str, str]:
|
||||
"""
|
||||
Create field mappings for strategy data transformation.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping field names to their corresponding data sources
|
||||
"""
|
||||
return {
|
||||
'business_objectives': 'website_analysis',
|
||||
'target_metrics': 'research_preferences',
|
||||
'content_budget': 'onboarding_session',
|
||||
'team_size': 'onboarding_session',
|
||||
'implementation_timeline': 'onboarding_session',
|
||||
'market_share': 'website_analysis',
|
||||
'competitive_position': 'website_analysis',
|
||||
'performance_metrics': 'website_analysis',
|
||||
'content_preferences': 'website_analysis',
|
||||
'consumption_patterns': 'research_preferences',
|
||||
'audience_pain_points': 'website_analysis',
|
||||
'buying_journey': 'website_analysis',
|
||||
'seasonal_trends': 'research_preferences',
|
||||
'engagement_metrics': 'website_analysis',
|
||||
'top_competitors': 'website_analysis',
|
||||
'competitor_content_strategies': 'website_analysis',
|
||||
'market_gaps': 'website_analysis',
|
||||
'industry_trends': 'website_analysis',
|
||||
'emerging_trends': 'website_analysis',
|
||||
'preferred_formats': 'website_analysis',
|
||||
'content_mix': 'research_preferences',
|
||||
'content_frequency': 'research_preferences',
|
||||
'optimal_timing': 'research_preferences',
|
||||
'quality_metrics': 'website_analysis',
|
||||
'editorial_guidelines': 'website_analysis',
|
||||
'brand_voice': 'website_analysis',
|
||||
'traffic_sources': 'website_analysis',
|
||||
'conversion_rates': 'website_analysis',
|
||||
'content_roi_targets': 'website_analysis',
|
||||
'ab_testing_capabilities': 'onboarding_session'
|
||||
}
|
||||
|
||||
|
||||
class StrategyUtils:
|
||||
"""
|
||||
Utility class for strategy-related operations.
|
||||
Provides static methods for strategy analysis and data processing.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def calculate_strategic_scores(ai_recommendations: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""Calculate strategic performance scores from AI recommendations."""
|
||||
return calculate_strategic_scores(ai_recommendations)
|
||||
|
||||
@staticmethod
|
||||
def extract_market_positioning(ai_recommendations: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Extract market positioning insights from AI recommendations."""
|
||||
return extract_market_positioning(ai_recommendations)
|
||||
|
||||
@staticmethod
|
||||
def extract_competitive_advantages(ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Extract competitive advantages from AI recommendations."""
|
||||
return extract_competitive_advantages(ai_recommendations)
|
||||
|
||||
@staticmethod
|
||||
def extract_strategic_risks(ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Extract strategic risks from AI recommendations."""
|
||||
return extract_strategic_risks(ai_recommendations)
|
||||
|
||||
@staticmethod
|
||||
def extract_opportunity_analysis(ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Extract opportunity analysis from AI recommendations."""
|
||||
return extract_opportunity_analysis(ai_recommendations)
|
||||
|
||||
@staticmethod
|
||||
def initialize_caches() -> Dict[str, Any]:
|
||||
"""Initialize in-memory caches for strategy operations."""
|
||||
return initialize_caches()
|
||||
|
||||
@staticmethod
|
||||
def calculate_data_quality_scores(data_sources: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""Calculate data quality scores for different data sources."""
|
||||
return calculate_data_quality_scores(data_sources)
|
||||
|
||||
@staticmethod
|
||||
def extract_content_preferences_from_style(writing_style: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Extract content preferences from writing style analysis."""
|
||||
return extract_content_preferences_from_style(writing_style)
|
||||
|
||||
@staticmethod
|
||||
def extract_brand_voice_from_guidelines(style_guidelines: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Extract brand voice from style guidelines."""
|
||||
return extract_brand_voice_from_guidelines(style_guidelines)
|
||||
|
||||
@staticmethod
|
||||
def extract_editorial_guidelines_from_style(writing_style: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Extract editorial guidelines from writing style analysis."""
|
||||
return extract_editorial_guidelines_from_style(writing_style)
|
||||
|
||||
@staticmethod
|
||||
def create_field_mappings() -> Dict[str, str]:
|
||||
"""Create field mappings for strategy data transformation."""
|
||||
return create_field_mappings()
|
||||
@@ -0,0 +1,473 @@
|
||||
"""
|
||||
Validation Service
|
||||
Data validation utilities.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Dict, Any, List, Optional, Union
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ValidationService:
|
||||
"""Service for data validation and business rule checking."""
|
||||
|
||||
def __init__(self):
|
||||
self.validation_patterns = {
|
||||
'email': re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'),
|
||||
'url': re.compile(r'^https?://(?:[-\w.])+(?:[:\d]+)?(?:/(?:[\w/_.])*(?:\?(?:[\w&=%.])*)?(?:#(?:[\w.])*)?)?$'),
|
||||
'phone': re.compile(r'^\+?1?\d{9,15}$'),
|
||||
'domain': re.compile(r'^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$'),
|
||||
'alphanumeric': re.compile(r'^[a-zA-Z0-9\s]+$'),
|
||||
'numeric': re.compile(r'^\d+(\.\d+)?$'),
|
||||
'integer': re.compile(r'^\d+$')
|
||||
}
|
||||
|
||||
self.business_rules = {
|
||||
'content_budget': {
|
||||
'min_value': 0,
|
||||
'max_value': 1000000,
|
||||
'required': True
|
||||
},
|
||||
'team_size': {
|
||||
'min_value': 1,
|
||||
'max_value': 100,
|
||||
'required': True
|
||||
},
|
||||
'implementation_timeline': {
|
||||
'min_days': 1,
|
||||
'max_days': 365,
|
||||
'required': True
|
||||
},
|
||||
'market_share': {
|
||||
'min_value': 0,
|
||||
'max_value': 100,
|
||||
'required': False
|
||||
}
|
||||
}
|
||||
|
||||
def validate_field(self, field_name: str, value: Any, field_type: str = 'string', **kwargs) -> Dict[str, Any]:
|
||||
"""Validate a single field."""
|
||||
try:
|
||||
validation_result = {
|
||||
'field_name': field_name,
|
||||
'value': value,
|
||||
'is_valid': True,
|
||||
'errors': [],
|
||||
'warnings': [],
|
||||
'validation_timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
# Check if value is required
|
||||
if kwargs.get('required', False) and (value is None or value == ''):
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' is required")
|
||||
return validation_result
|
||||
|
||||
# Skip validation if value is None and not required
|
||||
if value is None or value == '':
|
||||
return validation_result
|
||||
|
||||
# Type-specific validation
|
||||
if field_type == 'email':
|
||||
validation_result = self._validate_email(field_name, value, validation_result)
|
||||
elif field_type == 'url':
|
||||
validation_result = self._validate_url(field_name, value, validation_result)
|
||||
elif field_type == 'phone':
|
||||
validation_result = self._validate_phone(field_name, value, validation_result)
|
||||
elif field_type == 'domain':
|
||||
validation_result = self._validate_domain(field_name, value, validation_result)
|
||||
elif field_type == 'alphanumeric':
|
||||
validation_result = self._validate_alphanumeric(field_name, value, validation_result)
|
||||
elif field_type == 'numeric':
|
||||
validation_result = self._validate_numeric(field_name, value, validation_result)
|
||||
elif field_type == 'integer':
|
||||
validation_result = self._validate_integer(field_name, value, validation_result)
|
||||
elif field_type == 'date':
|
||||
validation_result = self._validate_date(field_name, value, validation_result)
|
||||
elif field_type == 'json':
|
||||
validation_result = self._validate_json(field_name, value, validation_result)
|
||||
else:
|
||||
validation_result = self._validate_string(field_name, value, validation_result)
|
||||
|
||||
# Length validation
|
||||
if 'min_length' in kwargs and len(str(value)) < kwargs['min_length']:
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must be at least {kwargs['min_length']} characters long")
|
||||
|
||||
if 'max_length' in kwargs and len(str(value)) > kwargs['max_length']:
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must be no more than {kwargs['max_length']} characters long")
|
||||
|
||||
# Range validation for numeric fields
|
||||
if field_type in ['numeric', 'integer']:
|
||||
if 'min_value' in kwargs and float(value) < kwargs['min_value']:
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must be at least {kwargs['min_value']}")
|
||||
|
||||
if 'max_value' in kwargs and float(value) > kwargs['max_value']:
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must be no more than {kwargs['max_value']}")
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating field {field_name}: {str(e)}")
|
||||
return {
|
||||
'field_name': field_name,
|
||||
'value': value,
|
||||
'is_valid': False,
|
||||
'errors': [f"Validation error: {str(e)}"],
|
||||
'warnings': [],
|
||||
'validation_timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
def validate_business_rules(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate data against business rules."""
|
||||
try:
|
||||
validation_result = {
|
||||
'is_valid': True,
|
||||
'errors': [],
|
||||
'warnings': [],
|
||||
'field_validations': {},
|
||||
'validation_timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
for field_name, rules in self.business_rules.items():
|
||||
if field_name in data:
|
||||
field_validation = self.validate_field(
|
||||
field_name,
|
||||
data[field_name],
|
||||
**rules
|
||||
)
|
||||
validation_result['field_validations'][field_name] = field_validation
|
||||
|
||||
if not field_validation['is_valid']:
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].extend(field_validation['errors'])
|
||||
|
||||
validation_result['warnings'].extend(field_validation['warnings'])
|
||||
elif rules.get('required', False):
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Required field '{field_name}' is missing")
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating business rules: {str(e)}")
|
||||
return {
|
||||
'is_valid': False,
|
||||
'errors': [f"Business rule validation error: {str(e)}"],
|
||||
'warnings': [],
|
||||
'field_validations': {},
|
||||
'validation_timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
def validate_strategy_data(self, strategy_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate content strategy data specifically."""
|
||||
try:
|
||||
validation_result = {
|
||||
'is_valid': True,
|
||||
'errors': [],
|
||||
'warnings': [],
|
||||
'field_validations': {},
|
||||
'validation_timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
# Required fields for content strategy
|
||||
required_fields = [
|
||||
'business_objectives', 'target_metrics', 'content_budget',
|
||||
'team_size', 'implementation_timeline'
|
||||
]
|
||||
|
||||
for field in required_fields:
|
||||
if field not in strategy_data or strategy_data[field] is None or strategy_data[field] == '':
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Required field '{field}' is missing")
|
||||
else:
|
||||
# Validate specific field types
|
||||
if field == 'content_budget':
|
||||
field_validation = self.validate_field(field, strategy_data[field], 'numeric', min_value=0, max_value=1000000)
|
||||
elif field == 'team_size':
|
||||
field_validation = self.validate_field(field, strategy_data[field], 'integer', min_value=1, max_value=100)
|
||||
elif field == 'implementation_timeline':
|
||||
field_validation = self.validate_field(field, strategy_data[field], 'string', min_length=1, max_length=500)
|
||||
else:
|
||||
field_validation = self.validate_field(field, strategy_data[field], 'string', min_length=1)
|
||||
|
||||
validation_result['field_validations'][field] = field_validation
|
||||
|
||||
if not field_validation['is_valid']:
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].extend(field_validation['errors'])
|
||||
|
||||
validation_result['warnings'].extend(field_validation['warnings'])
|
||||
|
||||
# Validate optional fields
|
||||
optional_fields = {
|
||||
'market_share': ('numeric', {'min_value': 0, 'max_value': 100}),
|
||||
'competitive_position': ('string', {'max_length': 1000}),
|
||||
'content_preferences': ('string', {'max_length': 2000}),
|
||||
'audience_pain_points': ('string', {'max_length': 2000}),
|
||||
'top_competitors': ('string', {'max_length': 1000}),
|
||||
'industry_trends': ('string', {'max_length': 1000})
|
||||
}
|
||||
|
||||
for field, (field_type, validation_params) in optional_fields.items():
|
||||
if field in strategy_data and strategy_data[field]:
|
||||
field_validation = self.validate_field(field, strategy_data[field], field_type, **validation_params)
|
||||
validation_result['field_validations'][field] = field_validation
|
||||
|
||||
if not field_validation['is_valid']:
|
||||
validation_result['warnings'].extend(field_validation['errors'])
|
||||
|
||||
validation_result['warnings'].extend(field_validation['warnings'])
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating strategy data: {str(e)}")
|
||||
return {
|
||||
'is_valid': False,
|
||||
'errors': [f"Strategy validation error: {str(e)}"],
|
||||
'warnings': [],
|
||||
'field_validations': {},
|
||||
'validation_timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
def _validate_email(self, field_name: str, value: str, validation_result: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate email format."""
|
||||
try:
|
||||
if not self.validation_patterns['email'].match(value):
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must be a valid email address")
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating email: {str(e)}")
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Email validation error: {str(e)}")
|
||||
return validation_result
|
||||
|
||||
def _validate_url(self, field_name: str, value: str, validation_result: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate URL format."""
|
||||
try:
|
||||
if not self.validation_patterns['url'].match(value):
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must be a valid URL")
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating URL: {str(e)}")
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"URL validation error: {str(e)}")
|
||||
return validation_result
|
||||
|
||||
def _validate_phone(self, field_name: str, value: str, validation_result: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate phone number format."""
|
||||
try:
|
||||
if not self.validation_patterns['phone'].match(value):
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must be a valid phone number")
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating phone: {str(e)}")
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Phone validation error: {str(e)}")
|
||||
return validation_result
|
||||
|
||||
def _validate_domain(self, field_name: str, value: str, validation_result: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate domain format."""
|
||||
try:
|
||||
if not self.validation_patterns['domain'].match(value):
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must be a valid domain")
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating domain: {str(e)}")
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Domain validation error: {str(e)}")
|
||||
return validation_result
|
||||
|
||||
def _validate_alphanumeric(self, field_name: str, value: str, validation_result: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate alphanumeric format."""
|
||||
try:
|
||||
if not self.validation_patterns['alphanumeric'].match(value):
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must contain only letters, numbers, and spaces")
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating alphanumeric: {str(e)}")
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Alphanumeric validation error: {str(e)}")
|
||||
return validation_result
|
||||
|
||||
def _validate_numeric(self, field_name: str, value: Union[str, int, float], validation_result: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate numeric format."""
|
||||
try:
|
||||
if isinstance(value, (int, float)):
|
||||
return validation_result
|
||||
|
||||
if not self.validation_patterns['numeric'].match(str(value)):
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must be a valid number")
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating numeric: {str(e)}")
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Numeric validation error: {str(e)}")
|
||||
return validation_result
|
||||
|
||||
def _validate_integer(self, field_name: str, value: Union[str, int], validation_result: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate integer format."""
|
||||
try:
|
||||
if isinstance(value, int):
|
||||
return validation_result
|
||||
|
||||
if not self.validation_patterns['integer'].match(str(value)):
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must be a valid integer")
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating integer: {str(e)}")
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Integer validation error: {str(e)}")
|
||||
return validation_result
|
||||
|
||||
def _validate_date(self, field_name: str, value: Union[str, datetime], validation_result: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate date format."""
|
||||
try:
|
||||
if isinstance(value, datetime):
|
||||
return validation_result
|
||||
|
||||
# Try to parse date string
|
||||
try:
|
||||
datetime.fromisoformat(str(value).replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must be a valid date")
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating date: {str(e)}")
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Date validation error: {str(e)}")
|
||||
return validation_result
|
||||
|
||||
def _validate_json(self, field_name: str, value: Union[str, dict, list], validation_result: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate JSON format."""
|
||||
try:
|
||||
if isinstance(value, (dict, list)):
|
||||
return validation_result
|
||||
|
||||
import json
|
||||
try:
|
||||
json.loads(str(value))
|
||||
except json.JSONDecodeError:
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must be valid JSON")
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating JSON: {str(e)}")
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"JSON validation error: {str(e)}")
|
||||
return validation_result
|
||||
|
||||
def _validate_string(self, field_name: str, value: str, validation_result: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate string format."""
|
||||
try:
|
||||
if not isinstance(value, str):
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"Field '{field_name}' must be a string")
|
||||
|
||||
return validation_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating string: {str(e)}")
|
||||
validation_result['is_valid'] = False
|
||||
validation_result['errors'].append(f"String validation error: {str(e)}")
|
||||
return validation_result
|
||||
|
||||
def generate_validation_error_message(self, validation_result: Dict[str, Any]) -> str:
|
||||
"""Generate a user-friendly error message from validation results."""
|
||||
try:
|
||||
if validation_result['is_valid']:
|
||||
return "Validation passed successfully"
|
||||
|
||||
if 'errors' in validation_result and validation_result['errors']:
|
||||
error_count = len(validation_result['errors'])
|
||||
if error_count == 1:
|
||||
return f"Validation error: {validation_result['errors'][0]}"
|
||||
else:
|
||||
return f"Validation failed with {error_count} errors: {'; '.join(validation_result['errors'])}"
|
||||
|
||||
return "Validation failed with unknown errors"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating validation error message: {str(e)}")
|
||||
return "Error generating validation message"
|
||||
|
||||
def get_validation_summary(self, validation_results: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""Generate a summary of multiple validation results."""
|
||||
try:
|
||||
summary = {
|
||||
'total_validations': len(validation_results),
|
||||
'passed_validations': 0,
|
||||
'failed_validations': 0,
|
||||
'total_errors': 0,
|
||||
'total_warnings': 0,
|
||||
'field_summary': {},
|
||||
'validation_timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
for result in validation_results:
|
||||
if result.get('is_valid', False):
|
||||
summary['passed_validations'] += 1
|
||||
else:
|
||||
summary['failed_validations'] += 1
|
||||
|
||||
summary['total_errors'] += len(result.get('errors', []))
|
||||
summary['total_warnings'] += len(result.get('warnings', []))
|
||||
|
||||
field_name = result.get('field_name', 'unknown')
|
||||
if field_name not in summary['field_summary']:
|
||||
summary['field_summary'][field_name] = {
|
||||
'validations': 0,
|
||||
'errors': 0,
|
||||
'warnings': 0
|
||||
}
|
||||
|
||||
summary['field_summary'][field_name]['validations'] += 1
|
||||
summary['field_summary'][field_name]['errors'] += len(result.get('errors', []))
|
||||
summary['field_summary'][field_name]['warnings'] += len(result.get('warnings', []))
|
||||
|
||||
return summary
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating validation summary: {str(e)}")
|
||||
return {
|
||||
'total_validations': 0,
|
||||
'passed_validations': 0,
|
||||
'failed_validations': 0,
|
||||
'total_errors': 0,
|
||||
'total_warnings': 0,
|
||||
'field_summary': {},
|
||||
'validation_timestamp': datetime.utcnow().isoformat(),
|
||||
'error': str(e)
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
"""
|
||||
Enhanced Strategy Database Service
|
||||
Handles database operations for enhanced content strategy functionality.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_, or_
|
||||
|
||||
# Import database models
|
||||
from models.enhanced_strategy_models import EnhancedContentStrategy, EnhancedAIAnalysisResult, OnboardingDataIntegration
|
||||
from models.enhanced_strategy_models import ContentStrategyAutofillInsights
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EnhancedStrategyDBService:
|
||||
"""Database service for enhanced content strategy operations."""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
async def get_enhanced_strategy(self, strategy_id: int) -> Optional[EnhancedContentStrategy]:
|
||||
"""Get an enhanced strategy by ID."""
|
||||
try:
|
||||
return self.db.query(EnhancedContentStrategy).filter(EnhancedContentStrategy.id == strategy_id).first()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting enhanced strategy {strategy_id}: {str(e)}")
|
||||
return None
|
||||
|
||||
async def get_enhanced_strategies(self, user_id: Optional[int] = None, strategy_id: Optional[int] = None) -> List[EnhancedContentStrategy]:
|
||||
"""Get enhanced strategies with optional filtering."""
|
||||
try:
|
||||
query = self.db.query(EnhancedContentStrategy)
|
||||
|
||||
if user_id:
|
||||
query = query.filter(EnhancedContentStrategy.user_id == user_id)
|
||||
|
||||
if strategy_id:
|
||||
query = query.filter(EnhancedContentStrategy.id == strategy_id)
|
||||
|
||||
return query.all()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting enhanced strategies: {str(e)}")
|
||||
return []
|
||||
|
||||
async def create_enhanced_strategy(self, strategy_data: Dict[str, Any]) -> Optional[EnhancedContentStrategy]:
|
||||
"""Create a new enhanced strategy."""
|
||||
try:
|
||||
strategy = EnhancedContentStrategy(**strategy_data)
|
||||
self.db.add(strategy)
|
||||
self.db.commit()
|
||||
self.db.refresh(strategy)
|
||||
return strategy
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating enhanced strategy: {str(e)}")
|
||||
self.db.rollback()
|
||||
return None
|
||||
|
||||
async def update_enhanced_strategy(self, strategy_id: int, update_data: Dict[str, Any]) -> Optional[EnhancedContentStrategy]:
|
||||
"""Update an enhanced strategy."""
|
||||
try:
|
||||
strategy = await self.get_enhanced_strategy(strategy_id)
|
||||
if not strategy:
|
||||
return None
|
||||
|
||||
for key, value in update_data.items():
|
||||
if hasattr(strategy, key):
|
||||
setattr(strategy, key, value)
|
||||
|
||||
strategy.updated_at = datetime.utcnow()
|
||||
self.db.commit()
|
||||
self.db.refresh(strategy)
|
||||
return strategy
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating enhanced strategy {strategy_id}: {str(e)}")
|
||||
self.db.rollback()
|
||||
return None
|
||||
|
||||
async def delete_enhanced_strategy(self, strategy_id: int) -> bool:
|
||||
"""Delete an enhanced strategy."""
|
||||
try:
|
||||
strategy = await self.get_enhanced_strategy(strategy_id)
|
||||
if not strategy:
|
||||
return False
|
||||
|
||||
self.db.delete(strategy)
|
||||
self.db.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting enhanced strategy {strategy_id}: {str(e)}")
|
||||
self.db.rollback()
|
||||
return False
|
||||
|
||||
async def get_enhanced_strategies_with_analytics(self, strategy_id: Optional[int] = None) -> List[Dict[str, Any]]:
|
||||
"""Get enhanced strategies with analytics data."""
|
||||
try:
|
||||
strategies = await self.get_enhanced_strategies(strategy_id=strategy_id)
|
||||
result = []
|
||||
|
||||
for strategy in strategies:
|
||||
strategy_dict = strategy.to_dict() if hasattr(strategy, 'to_dict') else {
|
||||
'id': strategy.id,
|
||||
'name': strategy.name,
|
||||
'industry': strategy.industry,
|
||||
'user_id': strategy.user_id,
|
||||
'created_at': strategy.created_at.isoformat() if strategy.created_at else None,
|
||||
'updated_at': strategy.updated_at.isoformat() if strategy.updated_at else None
|
||||
}
|
||||
|
||||
# Add analytics data
|
||||
analytics = await self.get_ai_analysis_history(strategy.id, limit=5)
|
||||
strategy_dict['analytics'] = analytics
|
||||
|
||||
result.append(strategy_dict)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting enhanced strategies with analytics: {str(e)}")
|
||||
return []
|
||||
|
||||
async def get_ai_analysis_history(self, strategy_id: int, limit: int = 10) -> List[Dict[str, Any]]:
|
||||
"""Get AI analysis history for a strategy."""
|
||||
try:
|
||||
analyses = self.db.query(EnhancedAIAnalysisResult).filter(
|
||||
EnhancedAIAnalysisResult.strategy_id == strategy_id
|
||||
).order_by(EnhancedAIAnalysisResult.created_at.desc()).limit(limit).all()
|
||||
|
||||
return [analysis.to_dict() if hasattr(analysis, 'to_dict') else {
|
||||
'id': analysis.id,
|
||||
'analysis_type': analysis.analysis_type,
|
||||
'insights': analysis.insights,
|
||||
'recommendations': analysis.recommendations,
|
||||
'created_at': analysis.created_at.isoformat() if analysis.created_at else None
|
||||
} for analysis in analyses]
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting AI analysis history for strategy {strategy_id}: {str(e)}")
|
||||
return []
|
||||
|
||||
async def get_onboarding_integration(self, strategy_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Get onboarding integration data for a strategy."""
|
||||
try:
|
||||
integration = self.db.query(OnboardingDataIntegration).filter(
|
||||
OnboardingDataIntegration.strategy_id == strategy_id
|
||||
).first()
|
||||
|
||||
if integration:
|
||||
return integration.to_dict() if hasattr(integration, 'to_dict') else {
|
||||
'id': integration.id,
|
||||
'strategy_id': integration.strategy_id,
|
||||
'data_sources': integration.data_sources,
|
||||
'confidence_scores': integration.confidence_scores,
|
||||
'created_at': integration.created_at.isoformat() if integration.created_at else None
|
||||
}
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting onboarding integration for strategy {strategy_id}: {str(e)}")
|
||||
return None
|
||||
|
||||
async def get_strategy_completion_stats(self, user_id: int) -> Dict[str, Any]:
|
||||
"""Get completion statistics for all strategies of a user."""
|
||||
try:
|
||||
strategies = await self.get_enhanced_strategies(user_id=user_id)
|
||||
|
||||
total_strategies = len(strategies)
|
||||
completed_strategies = sum(1 for s in strategies if s.completion_percentage >= 80)
|
||||
avg_completion = sum(s.completion_percentage for s in strategies) / total_strategies if total_strategies > 0 else 0
|
||||
|
||||
return {
|
||||
'total_strategies': total_strategies,
|
||||
'completed_strategies': completed_strategies,
|
||||
'avg_completion_percentage': avg_completion,
|
||||
'user_id': user_id
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting strategy completion stats for user {user_id}: {str(e)}")
|
||||
return {
|
||||
'total_strategies': 0,
|
||||
'completed_strategies': 0,
|
||||
'avg_completion_percentage': 0,
|
||||
'user_id': user_id
|
||||
}
|
||||
|
||||
async def search_enhanced_strategies(self, user_id: int, search_term: str) -> List[EnhancedContentStrategy]:
|
||||
"""Search enhanced strategies by name or industry."""
|
||||
try:
|
||||
return self.db.query(EnhancedContentStrategy).filter(
|
||||
and_(
|
||||
EnhancedContentStrategy.user_id == user_id,
|
||||
or_(
|
||||
EnhancedContentStrategy.name.ilike(f"%{search_term}%"),
|
||||
EnhancedContentStrategy.industry.ilike(f"%{search_term}%")
|
||||
)
|
||||
)
|
||||
).all()
|
||||
except Exception as e:
|
||||
logger.error(f"Error searching enhanced strategies: {str(e)}")
|
||||
return []
|
||||
|
||||
async def get_strategy_export_data(self, strategy_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Get comprehensive export data for a strategy."""
|
||||
try:
|
||||
strategy = await self.get_enhanced_strategy(strategy_id)
|
||||
if not strategy:
|
||||
return None
|
||||
|
||||
# Get strategy data
|
||||
strategy_data = strategy.to_dict() if hasattr(strategy, 'to_dict') else {
|
||||
'id': strategy.id,
|
||||
'name': strategy.name,
|
||||
'industry': strategy.industry,
|
||||
'user_id': strategy.user_id,
|
||||
'created_at': strategy.created_at.isoformat() if strategy.created_at else None,
|
||||
'updated_at': strategy.updated_at.isoformat() if strategy.updated_at else None
|
||||
}
|
||||
|
||||
# Get analytics data
|
||||
analytics = await self.get_ai_analysis_history(strategy_id, limit=10)
|
||||
|
||||
# Get onboarding integration
|
||||
onboarding = await self.get_onboarding_integration(strategy_id)
|
||||
|
||||
return {
|
||||
'strategy': strategy_data,
|
||||
'analytics': analytics,
|
||||
'onboarding_integration': onboarding,
|
||||
'exported_at': datetime.utcnow().isoformat()
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting strategy export data for strategy {strategy_id}: {str(e)}")
|
||||
return None
|
||||
|
||||
async def save_autofill_insights(self, *, strategy_id: int, user_id: int, payload: Dict[str, Any]) -> Optional[ContentStrategyAutofillInsights]:
|
||||
"""Persist accepted auto-fill inputs used to create a strategy."""
|
||||
try:
|
||||
record = ContentStrategyAutofillInsights(
|
||||
strategy_id=strategy_id,
|
||||
user_id=user_id,
|
||||
accepted_fields=payload.get('accepted_fields') or {},
|
||||
sources=payload.get('sources') or {},
|
||||
input_data_points=payload.get('input_data_points') or {},
|
||||
quality_scores=payload.get('quality_scores') or {},
|
||||
confidence_levels=payload.get('confidence_levels') or {},
|
||||
data_freshness=payload.get('data_freshness') or {}
|
||||
)
|
||||
self.db.add(record)
|
||||
self.db.commit()
|
||||
self.db.refresh(record)
|
||||
return record
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving autofill insights for strategy {strategy_id}: {str(e)}")
|
||||
self.db.rollback()
|
||||
return None
|
||||
|
||||
async def get_latest_autofill_insights(self, strategy_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Fetch the most recent accepted auto-fill snapshot for a strategy."""
|
||||
try:
|
||||
record = self.db.query(ContentStrategyAutofillInsights).filter(
|
||||
ContentStrategyAutofillInsights.strategy_id == strategy_id
|
||||
).order_by(ContentStrategyAutofillInsights.created_at.desc()).first()
|
||||
if not record:
|
||||
return None
|
||||
return {
|
||||
'id': record.id,
|
||||
'strategy_id': record.strategy_id,
|
||||
'user_id': record.user_id,
|
||||
'accepted_fields': record.accepted_fields,
|
||||
'sources': record.sources,
|
||||
'input_data_points': record.input_data_points,
|
||||
'quality_scores': record.quality_scores,
|
||||
'confidence_levels': record.confidence_levels,
|
||||
'data_freshness': record.data_freshness,
|
||||
'created_at': record.created_at.isoformat() if record.created_at else None
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching latest autofill insights for strategy {strategy_id}: {str(e)}")
|
||||
return None
|
||||
@@ -0,0 +1,235 @@
|
||||
"""
|
||||
Enhanced Strategy Service - Facade Module
|
||||
Thin facade that orchestrates modular content strategy components.
|
||||
This service delegates to specialized modules for better maintainability.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional, Union
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
# Import core strategy service
|
||||
from .content_strategy.core.strategy_service import EnhancedStrategyService as CoreStrategyService
|
||||
|
||||
# Import utilities
|
||||
from ..utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ..utils.response_builders import ResponseBuilder
|
||||
from ..utils.constants import ERROR_MESSAGES, SUCCESS_MESSAGES
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EnhancedStrategyService:
|
||||
"""
|
||||
Enhanced Strategy Service - Facade Implementation
|
||||
|
||||
This is a thin facade that orchestrates the modular content strategy components.
|
||||
All core functionality has been moved to specialized modules:
|
||||
- Core logic: content_strategy.core.strategy_service
|
||||
- Data processing: content_strategy.utils.data_processors
|
||||
- AI analysis: content_strategy.ai_analysis.strategy_analyzer
|
||||
- Strategy utilities: content_strategy.utils.strategy_utils
|
||||
"""
|
||||
|
||||
def __init__(self, db_service: Optional[Any] = None):
|
||||
"""Initialize the enhanced strategy service facade."""
|
||||
self.core_service = CoreStrategyService(db_service)
|
||||
self.db_service = db_service
|
||||
|
||||
# Performance optimization settings
|
||||
self.quality_thresholds = {
|
||||
'min_confidence': 0.7,
|
||||
'min_completeness': 0.8,
|
||||
'max_response_time': 30.0 # seconds
|
||||
}
|
||||
|
||||
# Performance optimization settings
|
||||
self.cache_settings = {
|
||||
'ai_analysis_cache_ttl': 3600, # 1 hour
|
||||
'onboarding_data_cache_ttl': 1800, # 30 minutes
|
||||
'strategy_cache_ttl': 7200, # 2 hours
|
||||
'max_cache_size': 1000 # Maximum cached items
|
||||
}
|
||||
|
||||
# Performance monitoring
|
||||
self.performance_metrics = {
|
||||
'response_times': [],
|
||||
'cache_hit_rates': {},
|
||||
'error_rates': {},
|
||||
'throughput_metrics': {}
|
||||
}
|
||||
|
||||
async def create_enhanced_strategy(self, strategy_data: Dict[str, Any], db: Session) -> Dict[str, Any]:
|
||||
"""Create a new enhanced content strategy - delegates to core service."""
|
||||
return await self.core_service.create_enhanced_strategy(strategy_data, db)
|
||||
|
||||
async def get_enhanced_strategies(self, user_id: Optional[int] = None, strategy_id: Optional[int] = None, db: Session = None) -> Dict[str, Any]:
|
||||
"""Get enhanced content strategies - delegates to core service."""
|
||||
return await self.core_service.get_enhanced_strategies(user_id, strategy_id, db)
|
||||
|
||||
async def _enhance_strategy_with_onboarding_data(self, strategy: Any, user_id: int, db: Session) -> None:
|
||||
"""Enhance strategy with onboarding data - delegates to core service."""
|
||||
return await self.core_service._enhance_strategy_with_onboarding_data(strategy, user_id, db)
|
||||
|
||||
async def _generate_comprehensive_ai_recommendations(self, strategy: Any, db: Session) -> None:
|
||||
"""Generate comprehensive AI recommendations - delegates to core service."""
|
||||
return await self.core_service.strategy_analyzer.generate_comprehensive_ai_recommendations(strategy, db)
|
||||
|
||||
async def _generate_specialized_recommendations(self, strategy: Any, analysis_type: str, db: Session) -> Dict[str, Any]:
|
||||
"""Generate specialized recommendations - delegates to core service."""
|
||||
return await self.core_service.strategy_analyzer.generate_specialized_recommendations(strategy, analysis_type, db)
|
||||
|
||||
def _create_specialized_prompt(self, strategy: Any, analysis_type: str) -> str:
|
||||
"""Create specialized AI prompts - delegates to core service."""
|
||||
return self.core_service.strategy_analyzer.create_specialized_prompt(strategy, analysis_type)
|
||||
|
||||
async def _call_ai_service(self, prompt: str, analysis_type: str) -> Dict[str, Any]:
|
||||
"""Call AI service - delegates to core service."""
|
||||
return await self.core_service.strategy_analyzer.call_ai_service(prompt, analysis_type)
|
||||
|
||||
def _parse_ai_response(self, ai_response: Dict[str, Any], analysis_type: str) -> Dict[str, Any]:
|
||||
"""Parse AI response - delegates to core service."""
|
||||
return self.core_service.strategy_analyzer.parse_ai_response(ai_response, analysis_type)
|
||||
|
||||
def _get_fallback_recommendations(self, analysis_type: str) -> Dict[str, Any]:
|
||||
"""Get fallback recommendations - delegates to core service."""
|
||||
return self.core_service.strategy_analyzer.get_fallback_recommendations(analysis_type)
|
||||
|
||||
def _extract_content_preferences_from_style(self, writing_style: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Extract content preferences from writing style - delegates to core service."""
|
||||
from .content_strategy.utils.strategy_utils import extract_content_preferences_from_style
|
||||
return extract_content_preferences_from_style(writing_style)
|
||||
|
||||
def _extract_brand_voice_from_guidelines(self, style_guidelines: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Extract brand voice from style guidelines - delegates to core service."""
|
||||
from .content_strategy.utils.strategy_utils import extract_brand_voice_from_guidelines
|
||||
return extract_brand_voice_from_guidelines(style_guidelines)
|
||||
|
||||
def _extract_editorial_guidelines_from_style(self, writing_style: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Extract editorial guidelines from writing style - delegates to core service."""
|
||||
from .content_strategy.utils.strategy_utils import extract_editorial_guidelines_from_style
|
||||
return extract_editorial_guidelines_from_style(writing_style)
|
||||
|
||||
def _create_field_mappings(self) -> Dict[str, str]:
|
||||
"""Create field mappings - delegates to core service."""
|
||||
from .content_strategy.utils.strategy_utils import create_field_mappings
|
||||
return create_field_mappings()
|
||||
|
||||
def _calculate_data_quality_scores(self, data_sources: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""Calculate data quality scores - delegates to core service."""
|
||||
from .content_strategy.utils.strategy_utils import calculate_data_quality_scores
|
||||
return calculate_data_quality_scores(data_sources)
|
||||
|
||||
def _calculate_confidence_levels(self, auto_populated_fields: Dict[str, str]) -> Dict[str, float]:
|
||||
"""Calculate confidence levels - deprecated, delegates to core service."""
|
||||
# deprecated; not used
|
||||
raise RuntimeError("Deprecated: use AutoFillService.quality")
|
||||
|
||||
def _calculate_confidence_levels_from_data(self, data_sources: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""Calculate confidence levels from data - deprecated, delegates to core service."""
|
||||
# deprecated; not used
|
||||
raise RuntimeError("Deprecated: use AutoFillService.quality")
|
||||
|
||||
def _calculate_data_freshness(self, onboarding_data: Union[Any, Dict[str, Any]]) -> Dict[str, str]:
|
||||
"""Calculate data freshness - deprecated, delegates to core service."""
|
||||
# deprecated; not used
|
||||
raise RuntimeError("Deprecated: use AutoFillService.quality")
|
||||
|
||||
def _calculate_strategic_scores(self, ai_recommendations: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""Calculate strategic performance scores - delegates to core service."""
|
||||
from .content_strategy.utils.strategy_utils import calculate_strategic_scores
|
||||
return calculate_strategic_scores(ai_recommendations)
|
||||
|
||||
def _extract_market_positioning(self, ai_recommendations: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Extract market positioning - delegates to core service."""
|
||||
from .content_strategy.utils.strategy_utils import extract_market_positioning
|
||||
return extract_market_positioning(ai_recommendations)
|
||||
|
||||
def _extract_competitive_advantages(self, ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Extract competitive advantages - delegates to core service."""
|
||||
from .content_strategy.utils.strategy_utils import extract_competitive_advantages
|
||||
return extract_competitive_advantages(ai_recommendations)
|
||||
|
||||
def _extract_strategic_risks(self, ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Extract strategic risks - delegates to core service."""
|
||||
from .content_strategy.utils.strategy_utils import extract_strategic_risks
|
||||
return extract_strategic_risks(ai_recommendations)
|
||||
|
||||
def _extract_opportunity_analysis(self, ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Extract opportunity analysis - delegates to core service."""
|
||||
from .content_strategy.utils.strategy_utils import extract_opportunity_analysis
|
||||
return extract_opportunity_analysis(ai_recommendations)
|
||||
|
||||
async def _get_latest_ai_analysis(self, strategy_id: int, db: Session) -> Optional[Dict[str, Any]]:
|
||||
"""Get latest AI analysis - delegates to core service."""
|
||||
return await self.core_service.strategy_analyzer.get_latest_ai_analysis(strategy_id, db)
|
||||
|
||||
async def _get_onboarding_integration(self, strategy_id: int, db: Session) -> Optional[Dict[str, Any]]:
|
||||
"""Get onboarding integration - delegates to core service."""
|
||||
return await self.core_service.strategy_analyzer.get_onboarding_integration(strategy_id, db)
|
||||
|
||||
async def _get_onboarding_data(self, user_id: int) -> Dict[str, Any]:
|
||||
"""Get comprehensive onboarding data - delegates to core service."""
|
||||
return await self.core_service.data_processor_service.get_onboarding_data(user_id)
|
||||
|
||||
def _transform_onboarding_data_to_fields(self, processed_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Transform onboarding data to fields - delegates to core service."""
|
||||
return self.core_service.data_processor_service.transform_onboarding_data_to_fields(processed_data)
|
||||
|
||||
def _get_data_sources(self, processed_data: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""Get data sources - delegates to core service."""
|
||||
return self.core_service.data_processor_service.get_data_sources(processed_data)
|
||||
|
||||
def _get_detailed_input_data_points(self, processed_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Get detailed input data points - delegates to core service."""
|
||||
return self.core_service.data_processor_service.get_detailed_input_data_points(processed_data)
|
||||
|
||||
def _get_fallback_onboarding_data(self) -> Dict[str, Any]:
|
||||
"""Get fallback onboarding data - delegates to core service."""
|
||||
return self.core_service.data_processor_service.get_fallback_onboarding_data()
|
||||
|
||||
async def _get_website_analysis_data(self, user_id: int) -> Dict[str, Any]:
|
||||
"""Get website analysis data - delegates to core service."""
|
||||
return await self.core_service.data_processor_service.get_website_analysis_data(user_id)
|
||||
|
||||
async def _get_research_preferences_data(self, user_id: int) -> Dict[str, Any]:
|
||||
"""Get research preferences data - delegates to core service."""
|
||||
return await self.core_service.data_processor_service.get_research_preferences_data(user_id)
|
||||
|
||||
async def _get_api_keys_data(self, user_id: int) -> Dict[str, Any]:
|
||||
"""Get API keys data - delegates to core service."""
|
||||
return await self.core_service.data_processor_service.get_api_keys_data(user_id)
|
||||
|
||||
async def _process_website_analysis(self, website_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Process website analysis - delegates to core service."""
|
||||
return await self.core_service.data_processor_service.process_website_analysis(website_data)
|
||||
|
||||
async def _process_research_preferences(self, research_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Process research preferences - delegates to core service."""
|
||||
return await self.core_service.data_processor_service.process_research_preferences(research_data)
|
||||
|
||||
async def _process_api_keys_data(self, api_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Process API keys data - delegates to core service."""
|
||||
return await self.core_service.data_processor_service.process_api_keys_data(api_data)
|
||||
|
||||
def _transform_onboarding_data_to_fields(self, processed_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# deprecated; not used
|
||||
raise RuntimeError("Deprecated: use AutoFillService.transformer")
|
||||
|
||||
def _get_data_sources(self, processed_data: Dict[str, Any]) -> Dict[str, str]:
|
||||
# deprecated; not used
|
||||
raise RuntimeError("Deprecated: use AutoFillService.transparency")
|
||||
|
||||
def _get_detailed_input_data_points(self, processed_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# deprecated; not used
|
||||
raise RuntimeError("Deprecated: use AutoFillService.transparency")
|
||||
|
||||
def _get_fallback_onboarding_data(self) -> Dict[str, Any]:
|
||||
"""Deprecated: fallbacks are no longer permitted. Kept for compatibility; always raises."""
|
||||
raise RuntimeError("Fallback onboarding data is disabled. Real data required.")
|
||||
|
||||
def _initialize_caches(self) -> None:
|
||||
"""Initialize caches - delegates to core service."""
|
||||
# This is now handled by the core service
|
||||
pass
|
||||
File diff suppressed because it is too large
Load Diff
268
backend/api/content_planning/services/gap_analysis_service.py
Normal file
268
backend/api/content_planning/services/gap_analysis_service.py
Normal file
@@ -0,0 +1,268 @@
|
||||
"""
|
||||
Gap Analysis Service for Content Planning API
|
||||
Extracted business logic from the gap analysis route for better separation of concerns.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
# Import database services
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
from services.ai_analysis_db_service import AIAnalysisDBService
|
||||
from services.onboarding.data_service import OnboardingDataService
|
||||
|
||||
# Import migrated content gap analysis services
|
||||
from services.content_gap_analyzer.content_gap_analyzer import ContentGapAnalyzer
|
||||
from services.content_gap_analyzer.competitor_analyzer import CompetitorAnalyzer
|
||||
from services.content_gap_analyzer.keyword_researcher import KeywordResearcher
|
||||
from services.content_gap_analyzer.ai_engine_service import AIEngineService
|
||||
from services.content_gap_analyzer.website_analyzer import WebsiteAnalyzer
|
||||
|
||||
# Import utilities
|
||||
from ..utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ..utils.response_builders import ResponseBuilder
|
||||
from ..utils.constants import ERROR_MESSAGES, SUCCESS_MESSAGES
|
||||
|
||||
class GapAnalysisService:
|
||||
"""Service class for content gap analysis operations."""
|
||||
|
||||
def __init__(self):
|
||||
self.ai_analysis_db_service = AIAnalysisDBService()
|
||||
self.onboarding_service = OnboardingDataService()
|
||||
|
||||
# Initialize migrated services
|
||||
self.content_gap_analyzer = ContentGapAnalyzer()
|
||||
self.competitor_analyzer = CompetitorAnalyzer()
|
||||
self.keyword_researcher = KeywordResearcher()
|
||||
self.ai_engine_service = AIEngineService()
|
||||
self.website_analyzer = WebsiteAnalyzer()
|
||||
|
||||
async def create_gap_analysis(self, analysis_data: Dict[str, Any], db: Session) -> Dict[str, Any]:
|
||||
"""Create a new content gap analysis."""
|
||||
try:
|
||||
logger.info(f"Creating content gap analysis for: {analysis_data.get('website_url', 'Unknown')}")
|
||||
|
||||
db_service = ContentPlanningDBService(db)
|
||||
created_analysis = await db_service.create_content_gap_analysis(analysis_data)
|
||||
|
||||
if created_analysis:
|
||||
logger.info(f"Content gap analysis created successfully: {created_analysis.id}")
|
||||
return created_analysis.to_dict()
|
||||
else:
|
||||
raise Exception("Failed to create gap analysis")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating content gap analysis: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "create_gap_analysis")
|
||||
|
||||
async def get_gap_analyses(self, user_id: Optional[int] = None, strategy_id: Optional[int] = None, force_refresh: bool = False) -> Dict[str, Any]:
|
||||
"""Get content gap analysis with real AI insights - Database first approach."""
|
||||
try:
|
||||
logger.info(f"🚀 Starting content gap analysis for user: {user_id}, strategy: {strategy_id}, force_refresh: {force_refresh}")
|
||||
|
||||
# Use user_id or default to 1
|
||||
current_user_id = user_id or 1
|
||||
|
||||
# Skip database check if force_refresh is True
|
||||
if not force_refresh:
|
||||
# First, try to get existing gap analysis from database
|
||||
logger.info(f"🔍 Checking database for existing gap analysis for user {current_user_id}")
|
||||
existing_analysis = await self.ai_analysis_db_service.get_latest_ai_analysis(
|
||||
user_id=current_user_id,
|
||||
analysis_type="gap_analysis",
|
||||
strategy_id=strategy_id,
|
||||
max_age_hours=24 # Use cached results up to 24 hours old
|
||||
)
|
||||
|
||||
if existing_analysis:
|
||||
logger.info(f"✅ Found existing gap analysis in database: {existing_analysis.get('id', 'unknown')}")
|
||||
|
||||
# Return cached results
|
||||
return {
|
||||
"gap_analyses": [{"recommendations": existing_analysis.get('recommendations', [])}],
|
||||
"total_gaps": len(existing_analysis.get('recommendations', [])),
|
||||
"generated_at": existing_analysis.get('created_at', datetime.utcnow()).isoformat(),
|
||||
"ai_service_status": existing_analysis.get('ai_service_status', 'operational'),
|
||||
"personalized_data_used": True if existing_analysis.get('personalized_data_used') else False,
|
||||
"data_source": "database_cache",
|
||||
"cache_age_hours": (datetime.utcnow() - existing_analysis.get('created_at', datetime.utcnow())).total_seconds() / 3600
|
||||
}
|
||||
|
||||
# No recent analysis found or force refresh requested, run new AI analysis
|
||||
logger.info(f"🔄 Running new gap analysis for user {current_user_id} (force_refresh: {force_refresh})")
|
||||
|
||||
# Get personalized inputs from onboarding data
|
||||
personalized_inputs = self.onboarding_service.get_personalized_ai_inputs(current_user_id)
|
||||
|
||||
logger.info(f"📊 Using personalized inputs: {len(personalized_inputs)} data points")
|
||||
|
||||
# Generate real AI-powered gap analysis
|
||||
gap_analysis = await self.ai_engine_service.generate_content_recommendations(personalized_inputs)
|
||||
|
||||
logger.info(f"✅ AI gap analysis completed: {len(gap_analysis)} recommendations")
|
||||
|
||||
# Store results in database
|
||||
try:
|
||||
await self.ai_analysis_db_service.store_ai_analysis_result(
|
||||
user_id=current_user_id,
|
||||
analysis_type="gap_analysis",
|
||||
insights=[],
|
||||
recommendations=gap_analysis,
|
||||
personalized_data=personalized_inputs,
|
||||
strategy_id=strategy_id,
|
||||
ai_service_status="operational"
|
||||
)
|
||||
logger.info(f"💾 Gap analysis results stored in database for user {current_user_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to store gap analysis in database: {str(e)}")
|
||||
|
||||
return {
|
||||
"gap_analyses": [{"recommendations": gap_analysis}],
|
||||
"total_gaps": len(gap_analysis),
|
||||
"generated_at": datetime.utcnow().isoformat(),
|
||||
"ai_service_status": "operational",
|
||||
"personalized_data_used": True,
|
||||
"data_source": "ai_analysis"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error generating content gap analysis: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_gap_analyses")
|
||||
|
||||
async def get_gap_analysis_by_id(self, analysis_id: int, db: Session) -> Dict[str, Any]:
|
||||
"""Get a specific content gap analysis by ID."""
|
||||
try:
|
||||
logger.info(f"Fetching content gap analysis: {analysis_id}")
|
||||
|
||||
db_service = ContentPlanningDBService(db)
|
||||
analysis = await db_service.get_content_gap_analysis(analysis_id)
|
||||
|
||||
if analysis:
|
||||
return analysis.to_dict()
|
||||
else:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Content gap analysis", analysis_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting content gap analysis: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_gap_analysis_by_id")
|
||||
|
||||
async def analyze_content_gaps(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Analyze content gaps between your website and competitors."""
|
||||
try:
|
||||
logger.info(f"Starting content gap analysis for: {request_data.get('website_url', 'Unknown')}")
|
||||
|
||||
# Use migrated services for actual analysis
|
||||
analysis_results = {}
|
||||
|
||||
# 1. Website Analysis
|
||||
logger.info("Performing website analysis...")
|
||||
website_analysis = await self.website_analyzer.analyze_website_content(request_data.get('website_url'))
|
||||
analysis_results['website_analysis'] = website_analysis
|
||||
|
||||
# 2. Competitor Analysis
|
||||
logger.info("Performing competitor analysis...")
|
||||
competitor_analysis = await self.competitor_analyzer.analyze_competitors(request_data.get('competitor_urls', []))
|
||||
analysis_results['competitor_analysis'] = competitor_analysis
|
||||
|
||||
# 3. Keyword Research
|
||||
logger.info("Performing keyword research...")
|
||||
keyword_analysis = await self.keyword_researcher.research_keywords(
|
||||
industry=request_data.get('industry'),
|
||||
target_keywords=request_data.get('target_keywords')
|
||||
)
|
||||
analysis_results['keyword_analysis'] = keyword_analysis
|
||||
|
||||
# 4. Content Gap Analysis
|
||||
logger.info("Performing content gap analysis...")
|
||||
gap_analysis = await self.content_gap_analyzer.identify_content_gaps(
|
||||
website_url=request_data.get('website_url'),
|
||||
competitor_urls=request_data.get('competitor_urls', []),
|
||||
keyword_data=keyword_analysis
|
||||
)
|
||||
analysis_results['gap_analysis'] = gap_analysis
|
||||
|
||||
# 5. AI-Powered Recommendations
|
||||
logger.info("Generating AI recommendations...")
|
||||
recommendations = await self.ai_engine_service.generate_recommendations(
|
||||
website_analysis=website_analysis,
|
||||
competitor_analysis=competitor_analysis,
|
||||
gap_analysis=gap_analysis,
|
||||
keyword_analysis=keyword_analysis
|
||||
)
|
||||
analysis_results['recommendations'] = recommendations
|
||||
|
||||
# 6. Strategic Opportunities
|
||||
logger.info("Identifying strategic opportunities...")
|
||||
opportunities = await self.ai_engine_service.identify_strategic_opportunities(
|
||||
gap_analysis=gap_analysis,
|
||||
competitor_analysis=competitor_analysis,
|
||||
keyword_analysis=keyword_analysis
|
||||
)
|
||||
analysis_results['opportunities'] = opportunities
|
||||
|
||||
# Prepare response
|
||||
response_data = {
|
||||
'website_analysis': analysis_results['website_analysis'],
|
||||
'competitor_analysis': analysis_results['competitor_analysis'],
|
||||
'gap_analysis': analysis_results['gap_analysis'],
|
||||
'recommendations': analysis_results['recommendations'],
|
||||
'opportunities': analysis_results['opportunities'],
|
||||
'created_at': datetime.utcnow()
|
||||
}
|
||||
|
||||
logger.info(f"Content gap analysis completed successfully")
|
||||
return response_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error analyzing content gaps: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "analyze_content_gaps")
|
||||
|
||||
async def get_user_gap_analyses(self, user_id: int, db: Session) -> List[Dict[str, Any]]:
|
||||
"""Get all gap analyses for a specific user."""
|
||||
try:
|
||||
logger.info(f"Fetching gap analyses for user: {user_id}")
|
||||
|
||||
db_service = ContentPlanningDBService(db)
|
||||
analyses = await db_service.get_user_content_gap_analyses(user_id)
|
||||
|
||||
return [analysis.to_dict() for analysis in analyses]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user gap analyses: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_user_gap_analyses")
|
||||
|
||||
async def update_gap_analysis(self, analysis_id: int, update_data: Dict[str, Any], db: Session) -> Dict[str, Any]:
|
||||
"""Update a content gap analysis."""
|
||||
try:
|
||||
logger.info(f"Updating content gap analysis: {analysis_id}")
|
||||
|
||||
db_service = ContentPlanningDBService(db)
|
||||
updated_analysis = await db_service.update_content_gap_analysis(analysis_id, update_data)
|
||||
|
||||
if updated_analysis:
|
||||
return updated_analysis.to_dict()
|
||||
else:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Content gap analysis", analysis_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating content gap analysis: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "update_gap_analysis")
|
||||
|
||||
async def delete_gap_analysis(self, analysis_id: int, db: Session) -> bool:
|
||||
"""Delete a content gap analysis."""
|
||||
try:
|
||||
logger.info(f"Deleting content gap analysis: {analysis_id}")
|
||||
|
||||
db_service = ContentPlanningDBService(db)
|
||||
deleted = await db_service.delete_content_gap_analysis(analysis_id)
|
||||
|
||||
if deleted:
|
||||
return True
|
||||
else:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Content gap analysis", analysis_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting content gap analysis: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "delete_gap_analysis")
|
||||
Reference in New Issue
Block a user