feat: image generation overhaul (model-aware text, dim clamping, \.30 pricing), event-driven dashboard cache invalidation, SEO insights (AI visibility, GSC, keyword gap), YouTube OAuth/publish, blog writer & content planning improvements, scheduler monitoring updates
This commit is contained in:
@@ -20,6 +20,9 @@ from ....services.enhanced_strategy_db_service import EnhancedStrategyDBService
|
||||
# Import educational content manager
|
||||
from .content_strategy.educational_content import EducationalContentManager
|
||||
|
||||
# Import authentication
|
||||
from middleware.auth_middleware import get_current_user
|
||||
|
||||
# Import utilities
|
||||
from ....utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ....utils.response_builders import ResponseBuilder
|
||||
@@ -40,13 +43,14 @@ _latest_strategies = {}
|
||||
|
||||
@router.post("/generate-comprehensive-strategy")
|
||||
async def generate_comprehensive_strategy(
|
||||
user_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
strategy_name: Optional[str] = None,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate a comprehensive AI-powered content strategy."""
|
||||
try:
|
||||
user_id = current_user.get('id')
|
||||
logger.info(f"🚀 Generating comprehensive AI strategy for user: {user_id}")
|
||||
|
||||
# Get user context and onboarding data
|
||||
@@ -103,7 +107,7 @@ async def generate_comprehensive_strategy(
|
||||
|
||||
@router.post("/generate-strategy-component")
|
||||
async def generate_strategy_component(
|
||||
user_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
component_type: str,
|
||||
base_strategy: Optional[Dict[str, Any]] = None,
|
||||
context: Optional[Dict[str, Any]] = None,
|
||||
@@ -111,6 +115,7 @@ async def generate_strategy_component(
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate a specific strategy component using AI."""
|
||||
try:
|
||||
user_id = current_user.get('id')
|
||||
logger.info(f"🚀 Generating strategy component '{component_type}' for user: {user_id}")
|
||||
|
||||
# Validate component type
|
||||
@@ -187,11 +192,12 @@ async def generate_strategy_component(
|
||||
|
||||
@router.get("/strategy-generation-status")
|
||||
async def get_strategy_generation_status(
|
||||
user_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get the status of strategy generation for a user."""
|
||||
try:
|
||||
user_id = current_user.get('id')
|
||||
logger.info(f"Getting strategy generation status for user: {user_id}")
|
||||
|
||||
# Get user's strategies
|
||||
@@ -247,6 +253,7 @@ async def get_strategy_generation_status(
|
||||
async def optimize_existing_strategy(
|
||||
strategy_id: int,
|
||||
optimization_type: str = "comprehensive",
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Optimize an existing strategy using AI."""
|
||||
@@ -309,12 +316,13 @@ async def optimize_existing_strategy(
|
||||
@router.post("/generate-comprehensive-strategy-polling")
|
||||
async def generate_comprehensive_strategy_polling(
|
||||
request: Dict[str, Any],
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate a comprehensive AI-powered content strategy using polling approach."""
|
||||
try:
|
||||
# Extract parameters from request body
|
||||
user_id = request.get("user_id", 1)
|
||||
user_id = current_user.get('id')
|
||||
strategy_name = request.get("strategy_name")
|
||||
config = request.get("config", {})
|
||||
|
||||
@@ -611,6 +619,7 @@ async def generate_comprehensive_strategy_polling(
|
||||
@router.get("/strategy-generation-status/{task_id}")
|
||||
async def get_strategy_generation_status_by_task(
|
||||
task_id: str,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get the status of strategy generation for a specific task."""
|
||||
@@ -647,11 +656,12 @@ async def get_strategy_generation_status_by_task(
|
||||
|
||||
@router.get("/latest-strategy")
|
||||
async def get_latest_generated_strategy(
|
||||
user_id: int = Query(1, description="User ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get the latest generated strategy from the polling system or database."""
|
||||
try:
|
||||
user_id = current_user.get('id')
|
||||
logger.info(f"🔍 Getting latest generated strategy for user: {user_id}")
|
||||
|
||||
# First, try to get from database (most reliable)
|
||||
|
||||
@@ -19,6 +19,9 @@ from ....services.enhanced_strategy_db_service import EnhancedStrategyDBService
|
||||
# Import models
|
||||
from models.enhanced_strategy_models import EnhancedContentStrategy, EnhancedAIAnalysisResult
|
||||
|
||||
# Import authentication
|
||||
from middleware.auth_middleware import get_current_user
|
||||
|
||||
# Import utilities
|
||||
from ....utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ....utils.response_builders import ResponseBuilder
|
||||
@@ -37,6 +40,7 @@ def get_db():
|
||||
@router.get("/{strategy_id}/analytics")
|
||||
async def get_enhanced_strategy_analytics(
|
||||
strategy_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get comprehensive analytics for an enhanced strategy."""
|
||||
@@ -72,6 +76,7 @@ async def get_enhanced_strategy_analytics(
|
||||
async def get_enhanced_strategy_ai_analysis(
|
||||
strategy_id: int,
|
||||
limit: int = Query(10, description="Number of AI analysis results to return"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get AI analysis history for an enhanced strategy."""
|
||||
@@ -108,6 +113,7 @@ async def get_enhanced_strategy_ai_analysis(
|
||||
@router.get("/{strategy_id}/completion")
|
||||
async def get_enhanced_strategy_completion_stats(
|
||||
strategy_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get completion statistics for an enhanced strategy."""
|
||||
@@ -147,6 +153,7 @@ async def get_enhanced_strategy_completion_stats(
|
||||
@router.get("/{strategy_id}/onboarding-integration")
|
||||
async def get_enhanced_strategy_onboarding_integration(
|
||||
strategy_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get onboarding data integration for an enhanced strategy."""
|
||||
@@ -177,6 +184,7 @@ async def get_enhanced_strategy_onboarding_integration(
|
||||
@router.post("/{strategy_id}/ai-recommendations")
|
||||
async def generate_enhanced_ai_recommendations(
|
||||
strategy_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate AI recommendations for an enhanced strategy."""
|
||||
@@ -216,6 +224,7 @@ async def generate_enhanced_ai_recommendations(
|
||||
async def regenerate_enhanced_strategy_ai_analysis(
|
||||
strategy_id: int,
|
||||
analysis_type: str,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Regenerate AI analysis for an enhanced strategy."""
|
||||
|
||||
@@ -21,6 +21,9 @@ from ....services.enhanced_strategy_service import EnhancedStrategyService
|
||||
from ....services.enhanced_strategy_db_service import EnhancedStrategyDBService
|
||||
from ....services.content_strategy.autofill.ai_refresh import AutoFillRefreshService
|
||||
|
||||
# Import authentication
|
||||
from middleware.auth_middleware import get_current_user
|
||||
|
||||
# Import utilities
|
||||
from ....utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ....utils.response_builders import ResponseBuilder
|
||||
@@ -49,12 +52,13 @@ async def stream_data(data_generator):
|
||||
async def accept_autofill_inputs(
|
||||
strategy_id: int,
|
||||
payload: Dict[str, Any],
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Persist end-user accepted auto-fill inputs and associate with the strategy."""
|
||||
try:
|
||||
logger.info(f"🚀 Accepting autofill inputs for strategy: {strategy_id}")
|
||||
user_id = str(payload.get('user_id') or "")
|
||||
user_id = str(current_user.get('id'))
|
||||
accepted_fields = payload.get('accepted_fields') or {}
|
||||
# Optional transparency bundles
|
||||
sources = payload.get('sources') or {}
|
||||
@@ -99,7 +103,7 @@ async def accept_autofill_inputs(
|
||||
|
||||
@router.get("/autofill/refresh/stream")
|
||||
async def stream_autofill_refresh(
|
||||
user_id: Optional[int] = Query(None, description="User ID to build auto-fill for"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
use_ai: bool = Query(True, description="Use AI augmentation during refresh"),
|
||||
ai_only: bool = Query(False, description="AI-first refresh: return AI overrides when available"),
|
||||
db: Session = Depends(get_db)
|
||||
@@ -107,7 +111,7 @@ async def stream_autofill_refresh(
|
||||
"""SSE endpoint to stream steps while generating a fresh auto-fill payload (no DB writes)."""
|
||||
async def refresh_generator():
|
||||
try:
|
||||
actual_user_id = user_id or 1
|
||||
actual_user_id = current_user.get('id', 1)
|
||||
start_time = datetime.utcnow()
|
||||
logger.info(f"🚀 Starting auto-fill refresh stream for user: {actual_user_id}")
|
||||
yield {"type": "status", "phase": "init", "message": "Starting…", "progress": 5}
|
||||
@@ -203,14 +207,14 @@ async def stream_autofill_refresh(
|
||||
|
||||
@router.post("/autofill/refresh")
|
||||
async def refresh_autofill(
|
||||
user_id: Optional[int] = Query(None, description="User ID to build auto-fill for"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
use_ai: bool = Query(True, description="Use AI augmentation during refresh"),
|
||||
ai_only: bool = Query(False, description="AI-first refresh: return AI overrides when available"),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Non-stream endpoint to return a fresh auto-fill payload (no DB writes)."""
|
||||
try:
|
||||
actual_user_id = user_id or 1
|
||||
actual_user_id = current_user.get('id', 1)
|
||||
started = datetime.utcnow()
|
||||
refresh_service = AutoFillRefreshService(db)
|
||||
payload = await refresh_service.build_fresh_payload_with_transparency(actual_user_id, use_ai=use_ai, ai_only=ai_only)
|
||||
|
||||
@@ -4,7 +4,7 @@ Handles streaming endpoints for enhanced content strategies.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi.responses import StreamingResponse
|
||||
from starlette.requests import Request
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -12,8 +12,6 @@ from loguru import logger
|
||||
import json
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from collections import defaultdict
|
||||
import time
|
||||
|
||||
# Import database
|
||||
from services.database import get_db_session
|
||||
@@ -25,31 +23,13 @@ from middleware.auth_middleware import get_current_user, get_current_user_with_q
|
||||
from ....services.enhanced_strategy_service import EnhancedStrategyService
|
||||
from ....services.enhanced_strategy_db_service import EnhancedStrategyDBService
|
||||
|
||||
# Import utilities
|
||||
from ....utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ....utils.response_builders import ResponseBuilder
|
||||
from ....utils.constants import ERROR_MESSAGES, SUCCESS_MESSAGES
|
||||
# Use bounded shared cache instead of process-local unbounded dict
|
||||
from ...services.content_strategy.performance.caching import CachingService
|
||||
|
||||
router = APIRouter(tags=["Strategy Streaming"])
|
||||
|
||||
# Cache for streaming endpoints (5 minutes cache)
|
||||
streaming_cache = defaultdict(dict)
|
||||
CACHE_DURATION = 300 # 5 minutes
|
||||
|
||||
def get_cached_data(cache_key: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get cached data if it exists and is not expired."""
|
||||
if cache_key in streaming_cache:
|
||||
cached_data = streaming_cache[cache_key]
|
||||
if time.time() - cached_data.get("timestamp", 0) < CACHE_DURATION:
|
||||
return cached_data.get("data")
|
||||
return None
|
||||
|
||||
def set_cached_data(cache_key: str, data: Dict[str, Any]):
|
||||
"""Set cached data with timestamp."""
|
||||
streaming_cache[cache_key] = {
|
||||
"data": data,
|
||||
"timestamp": time.time()
|
||||
}
|
||||
# Shared bounded cache for streaming endpoints
|
||||
streaming_cache_service = CachingService()
|
||||
|
||||
# Helper function to get database session
|
||||
def get_db():
|
||||
@@ -123,11 +103,7 @@ async def stream_enhanced_strategies(
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
||||
"Access-Control-Allow-Credentials": "true"
|
||||
"Connection": "keep-alive"
|
||||
}
|
||||
)
|
||||
|
||||
@@ -150,9 +126,9 @@ async def stream_strategic_intelligence(
|
||||
|
||||
logger.info(f"🚀 Starting strategic intelligence stream for authenticated user: {authenticated_user_id}")
|
||||
|
||||
# Check cache first
|
||||
# Check bounded shared cache first
|
||||
cache_key = f"strategic_intelligence_{authenticated_user_id}"
|
||||
cached_data = get_cached_data(cache_key)
|
||||
cached_data = await streaming_cache_service.get_cached_data("streaming_intelligence", cache_key)
|
||||
if cached_data:
|
||||
logger.info(f"✅ Returning cached strategic intelligence data for user: {authenticated_user_id}")
|
||||
yield {"type": "result", "status": "success", "data": cached_data, "progress": 100}
|
||||
@@ -167,7 +143,6 @@ async def stream_strategic_intelligence(
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Retrieving strategies...", "progress": 20}
|
||||
|
||||
# Use authenticated user_id to ensure users can only see their own strategies
|
||||
strategies_data = await enhanced_service.get_enhanced_strategies(authenticated_user_id, None, db)
|
||||
|
||||
# Send progress update
|
||||
@@ -194,54 +169,29 @@ async def stream_strategic_intelligence(
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Processing intelligence data...", "progress": 60}
|
||||
|
||||
# Build strategic intelligence from actual strategy data — no hardcoded fallback defaults
|
||||
strategic_intelligence = {
|
||||
"market_positioning": {
|
||||
"current_position": strategy.get("competitive_position", "Challenger"),
|
||||
"target_position": "Market Leader",
|
||||
"differentiation_factors": [
|
||||
"AI-powered content optimization",
|
||||
"Data-driven strategy development",
|
||||
"Personalized user experience"
|
||||
]
|
||||
"current_position": strategy.get("competitive_position") or None,
|
||||
"differentiation_factors": strategy.get("differentiation_factors") or None
|
||||
},
|
||||
"competitive_analysis": {
|
||||
"top_competitors": strategy.get("top_competitors", [])[:3] or [
|
||||
"Competitor A", "Competitor B", "Competitor C"
|
||||
],
|
||||
"competitive_advantages": [
|
||||
"Advanced AI capabilities",
|
||||
"Comprehensive data integration",
|
||||
"User-centric design"
|
||||
],
|
||||
"market_gaps": strategy.get("market_gaps", []) or [
|
||||
"AI-driven content personalization",
|
||||
"Real-time performance optimization",
|
||||
"Predictive analytics"
|
||||
]
|
||||
"top_competitors": (strategy.get("top_competitors") or [None])[:3],
|
||||
"competitive_advantages": strategy.get("competitive_advantages") or None,
|
||||
"market_gaps": strategy.get("market_gaps") or None
|
||||
},
|
||||
"ai_insights": ai_recommendations.get("strategic_insights", []) or [
|
||||
"Focus on pillar content strategy",
|
||||
"Implement topic clustering",
|
||||
"Optimize for voice search"
|
||||
],
|
||||
"opportunities": [
|
||||
{
|
||||
"area": "Content Personalization",
|
||||
"potential_impact": "High",
|
||||
"implementation_timeline": "3-6 months",
|
||||
"estimated_roi": "25-40%"
|
||||
},
|
||||
{
|
||||
"area": "AI-Powered Optimization",
|
||||
"potential_impact": "Medium",
|
||||
"implementation_timeline": "6-12 months",
|
||||
"estimated_roi": "15-30%"
|
||||
}
|
||||
]
|
||||
"ai_insights": ai_recommendations.get("strategic_insights") if ai_recommendations else None,
|
||||
"opportunities": strategy.get("opportunities") or None
|
||||
}
|
||||
|
||||
# Filter out null-only sections for cleaner responses
|
||||
strategic_intelligence = {
|
||||
k: v for k, v in strategic_intelligence.items()
|
||||
if v is not None and v != [None]
|
||||
}
|
||||
|
||||
# Cache the strategic intelligence data
|
||||
set_cached_data(cache_key, strategic_intelligence)
|
||||
await streaming_cache_service.set_cached_data("streaming_intelligence", cache_key, strategic_intelligence)
|
||||
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Finalizing strategic intelligence...", "progress": 80}
|
||||
@@ -260,11 +210,7 @@ async def stream_strategic_intelligence(
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
||||
"Access-Control-Allow-Credentials": "true"
|
||||
"Connection": "keep-alive"
|
||||
}
|
||||
)
|
||||
|
||||
@@ -287,9 +233,9 @@ async def stream_keyword_research(
|
||||
|
||||
logger.info(f"🚀 Starting keyword research stream for authenticated user: {authenticated_user_id}")
|
||||
|
||||
# Check cache first
|
||||
# Check bounded shared cache first
|
||||
cache_key = f"keyword_research_{authenticated_user_id}"
|
||||
cached_data = get_cached_data(cache_key)
|
||||
cached_data = await streaming_cache_service.get_cached_data("streaming_intelligence", cache_key)
|
||||
if cached_data:
|
||||
logger.info(f"✅ Returning cached keyword research data for user: {authenticated_user_id}")
|
||||
yield {"type": "result", "status": "success", "data": cached_data, "progress": 100}
|
||||
@@ -333,33 +279,24 @@ async def stream_keyword_research(
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Processing keyword data...", "progress": 60}
|
||||
|
||||
# Build keyword data from actual analysis — no hardcoded fallback defaults
|
||||
keyword_data = {
|
||||
"trend_analysis": {
|
||||
"high_volume_keywords": analysis_results.get("opportunities", [])[:3] or [
|
||||
{"keyword": "AI marketing automation", "volume": "10K-100K", "difficulty": "Medium"},
|
||||
{"keyword": "content strategy 2024", "volume": "1K-10K", "difficulty": "Low"},
|
||||
{"keyword": "digital marketing trends", "volume": "10K-100K", "difficulty": "High"}
|
||||
],
|
||||
"trending_keywords": [
|
||||
{"keyword": "AI content generation", "growth": "+45%", "opportunity": "High"},
|
||||
{"keyword": "voice search optimization", "growth": "+32%", "opportunity": "Medium"},
|
||||
{"keyword": "video marketing strategy", "growth": "+28%", "opportunity": "High"}
|
||||
]
|
||||
"high_volume_keywords": (analysis_results.get("opportunities") or [None])[:3],
|
||||
"trending_keywords": analysis_results.get("trending_keywords") or None
|
||||
},
|
||||
"intent_analysis": {
|
||||
"informational": ["how to", "what is", "guide to"],
|
||||
"navigational": ["company name", "brand name", "website"],
|
||||
"transactional": ["buy", "purchase", "download", "sign up"]
|
||||
},
|
||||
"opportunities": analysis_results.get("opportunities", []) or [
|
||||
{"keyword": "AI content tools", "search_volume": "5K-10K", "competition": "Low", "cpc": "$2.50"},
|
||||
{"keyword": "content marketing ROI", "search_volume": "1K-5K", "competition": "Medium", "cpc": "$4.20"},
|
||||
{"keyword": "social media strategy", "search_volume": "10K-50K", "competition": "High", "cpc": "$3.80"}
|
||||
]
|
||||
"intent_analysis": analysis_results.get("intent_analysis") or None,
|
||||
"opportunities": analysis_results.get("opportunities") or None
|
||||
}
|
||||
|
||||
# Filter out null-only sections
|
||||
keyword_data = {
|
||||
k: v for k, v in keyword_data.items()
|
||||
if v is not None and v != [None]
|
||||
}
|
||||
|
||||
# Cache the keyword data
|
||||
set_cached_data(cache_key, keyword_data)
|
||||
await streaming_cache_service.set_cached_data("streaming_intelligence", cache_key, keyword_data)
|
||||
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Finalizing keyword research...", "progress": 80}
|
||||
@@ -378,10 +315,71 @@ async def stream_keyword_research(
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
||||
"Access-Control-Allow-Credentials": "true"
|
||||
"Connection": "keep-alive"
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@router.get("/stream/ai-generation-status")
|
||||
async def stream_ai_generation_status(
|
||||
request: Request,
|
||||
strategy_id: int = Query(..., description="Strategy ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_with_query_token),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Stream AI generation status for a strategy with real-time updates."""
|
||||
|
||||
async def status_generator():
|
||||
try:
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
if not clerk_user_id:
|
||||
yield {"type": "error", "detail": "Invalid user ID", "progress": 0}
|
||||
return
|
||||
|
||||
authenticated_user_id = clerk_user_id
|
||||
|
||||
logger.info(f"🚀 Starting AI generation status stream for user: {authenticated_user_id}, strategy: {strategy_id}")
|
||||
|
||||
yield {"type": "progress", "detail": "Fetching AI generation status...", "progress": 10}
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
enhanced_service = EnhancedStrategyService(db_service)
|
||||
|
||||
strategy = await enhanced_service.get_enhanced_strategy(strategy_id, authenticated_user_id, db)
|
||||
|
||||
if not strategy or strategy.get("status") == "not_found":
|
||||
yield {"type": "error", "detail": "Strategy not found", "progress": 0}
|
||||
return
|
||||
|
||||
yield {"type": "progress", "detail": "Checking AI analysis status...", "progress": 30}
|
||||
|
||||
ai_recommendations = strategy.get("ai_recommendations")
|
||||
if ai_recommendations:
|
||||
if isinstance(ai_recommendations, str):
|
||||
try:
|
||||
ai_recommendations = json.loads(ai_recommendations)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
ai_recommendations = {}
|
||||
|
||||
ai_status = "completed" if ai_recommendations else "pending"
|
||||
|
||||
if ai_status == "completed":
|
||||
yield {"type": "progress", "detail": "AI analysis completed", "progress": 80}
|
||||
yield {"type": "result", "status": "completed", "detail": "AI generation completed", "progress": 100}
|
||||
else:
|
||||
yield {"type": "progress", "detail": "AI analysis is pending", "progress": 50}
|
||||
yield {"type": "result", "status": "pending", "detail": "AI generation is in progress", "progress": 50}
|
||||
|
||||
logger.info(f"✅ AI generation status stream completed for user: {authenticated_user_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error in AI generation status stream: {str(e)}")
|
||||
yield {"type": "error", "detail": str(e), "progress": 0}
|
||||
|
||||
return StreamingResponse(
|
||||
stream_data(status_generator()),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive"
|
||||
}
|
||||
)
|
||||
|
||||
@@ -65,12 +65,16 @@ async def analyze_content_evolution(
|
||||
)
|
||||
|
||||
@router.post("/performance-trends", response_model=AIAnalyticsResponse)
|
||||
async def analyze_performance_trends(request: PerformanceTrendsRequest):
|
||||
async def analyze_performance_trends(
|
||||
request: PerformanceTrendsRequest,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Analyze performance trends for content strategy.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Starting performance trends analysis for strategy {request.strategy_id}")
|
||||
user_id = current_user.get("user_id")
|
||||
logger.info(f"Starting performance trends analysis for strategy {request.strategy_id} (user {user_id})")
|
||||
|
||||
result = await ai_analytics_service.analyze_performance_trends(
|
||||
strategy_id=request.strategy_id,
|
||||
@@ -87,12 +91,16 @@ async def analyze_performance_trends(request: PerformanceTrendsRequest):
|
||||
)
|
||||
|
||||
@router.post("/predict-performance", response_model=AIAnalyticsResponse)
|
||||
async def predict_content_performance(request: ContentPerformancePredictionRequest):
|
||||
async def predict_content_performance(
|
||||
request: ContentPerformancePredictionRequest,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Predict content performance using AI models.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Starting content performance prediction for strategy {request.strategy_id}")
|
||||
user_id = current_user.get("user_id")
|
||||
logger.info(f"Starting content performance prediction for strategy {request.strategy_id} (user {user_id})")
|
||||
|
||||
result = await ai_analytics_service.predict_content_performance(
|
||||
strategy_id=request.strategy_id,
|
||||
@@ -137,12 +145,13 @@ async def generate_strategic_intelligence(
|
||||
|
||||
@router.get("/", response_model=Dict[str, Any])
|
||||
async def get_ai_analytics(
|
||||
user_id: Optional[int] = Query(None, description="User ID"),
|
||||
strategy_id: Optional[int] = Query(None, description="Strategy ID"),
|
||||
force_refresh: bool = Query(False, description="Force refresh AI analysis")
|
||||
force_refresh: bool = Query(False, description="Force refresh AI analysis"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||
):
|
||||
"""Get AI analytics with real personalized insights - Database first approach."""
|
||||
try:
|
||||
user_id = current_user.get("user_id") or current_user.get("id")
|
||||
logger.info(f"🚀 Starting AI analytics for user: {user_id}, strategy: {strategy_id}, force_refresh: {force_refresh}")
|
||||
|
||||
result = await ai_analytics_service.get_ai_analytics(user_id, strategy_id, force_refresh)
|
||||
@@ -153,11 +162,14 @@ async def get_ai_analytics(
|
||||
raise HTTPException(status_code=500, detail=f"Error generating AI analytics: {str(e)}")
|
||||
|
||||
@router.get("/health")
|
||||
async def ai_analytics_health_check():
|
||||
async def ai_analytics_health_check(
|
||||
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Health check for AI analytics services.
|
||||
"""
|
||||
try:
|
||||
logger.debug(f"AI analytics health check by user: {current_user.get('id')}")
|
||||
# Check AI analytics service
|
||||
service_status = {}
|
||||
|
||||
@@ -197,14 +209,16 @@ async def ai_analytics_health_check():
|
||||
async def get_user_ai_analysis_results(
|
||||
user_id: int,
|
||||
analysis_type: Optional[str] = Query(None, description="Filter by analysis type"),
|
||||
limit: int = Query(10, description="Number of results to return")
|
||||
limit: int = Query(10, description="Number of results to return"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||
):
|
||||
"""Get AI analysis results for a specific user."""
|
||||
"""Get AI analysis results for the authenticated user."""
|
||||
try:
|
||||
logger.info(f"Fetching AI analysis results for user {user_id}")
|
||||
authenticated_user_id = current_user.get("user_id") or current_user.get("id")
|
||||
logger.info(f"Fetching AI analysis results for authenticated user {authenticated_user_id}")
|
||||
|
||||
result = await ai_analytics_service.get_user_ai_analysis_results(
|
||||
user_id=user_id,
|
||||
user_id=authenticated_user_id,
|
||||
analysis_type=analysis_type,
|
||||
limit=limit
|
||||
)
|
||||
@@ -219,14 +233,16 @@ async def get_user_ai_analysis_results(
|
||||
async def refresh_ai_analysis(
|
||||
user_id: int,
|
||||
analysis_type: str = Query(..., description="Type of analysis to refresh"),
|
||||
strategy_id: Optional[int] = Query(None, description="Strategy ID")
|
||||
strategy_id: Optional[int] = Query(None, description="Strategy ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||
):
|
||||
"""Force refresh of AI analysis for a user."""
|
||||
"""Force refresh of AI analysis for the authenticated user."""
|
||||
try:
|
||||
logger.info(f"Force refreshing AI analysis for user {user_id}, type: {analysis_type}")
|
||||
authenticated_user_id = current_user.get("user_id") or current_user.get("id")
|
||||
logger.info(f"Force refreshing AI analysis for authenticated user {authenticated_user_id}, type: {analysis_type}")
|
||||
|
||||
result = await ai_analytics_service.refresh_ai_analysis(
|
||||
user_id=user_id,
|
||||
user_id=authenticated_user_id,
|
||||
analysis_type=analysis_type,
|
||||
strategy_id=strategy_id
|
||||
)
|
||||
@@ -240,14 +256,16 @@ async def refresh_ai_analysis(
|
||||
@router.delete("/cache/{user_id}")
|
||||
async def clear_ai_analysis_cache(
|
||||
user_id: int,
|
||||
analysis_type: Optional[str] = Query(None, description="Specific analysis type to clear")
|
||||
analysis_type: Optional[str] = Query(None, description="Specific analysis type to clear"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||
):
|
||||
"""Clear AI analysis cache for a user."""
|
||||
"""Clear AI analysis cache for the authenticated user."""
|
||||
try:
|
||||
logger.info(f"Clearing AI analysis cache for user {user_id}")
|
||||
authenticated_user_id = current_user.get("user_id") or current_user.get("id")
|
||||
logger.info(f"Clearing AI analysis cache for authenticated user {authenticated_user_id}")
|
||||
|
||||
result = await ai_analytics_service.clear_ai_analysis_cache(
|
||||
user_id=user_id,
|
||||
user_id=authenticated_user_id,
|
||||
analysis_type=analysis_type
|
||||
)
|
||||
|
||||
@@ -259,13 +277,15 @@ async def clear_ai_analysis_cache(
|
||||
|
||||
@router.get("/statistics")
|
||||
async def get_ai_analysis_statistics(
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
user_id: Optional[int] = Query(None, description="User ID for user-specific stats")
|
||||
):
|
||||
"""Get AI analysis statistics."""
|
||||
try:
|
||||
logger.info(f"📊 Getting AI analysis statistics for user: {user_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"📊 Getting AI analysis statistics for authenticated user: {clerk_user_id}")
|
||||
|
||||
result = await ai_analytics_service.get_ai_analysis_statistics(user_id)
|
||||
result = await ai_analytics_service.get_ai_analysis_statistics(user_id or clerk_user_id)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -9,6 +9,9 @@ from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
|
||||
# Import authentication
|
||||
from middleware.auth_middleware import get_current_user
|
||||
|
||||
# Import database service
|
||||
from services.database import get_db_session, get_db
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
@@ -34,13 +37,16 @@ router = APIRouter(prefix="/calendar-events", tags=["calendar-events"])
|
||||
@router.post("/", response_model=CalendarEventResponse)
|
||||
async def create_calendar_event(
|
||||
event: CalendarEventCreate,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create a new calendar event."""
|
||||
try:
|
||||
logger.info(f"Creating calendar event: {event.title}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Creating calendar event: {event.title} for user: {clerk_user_id}")
|
||||
|
||||
event_data = event.dict()
|
||||
event_data['user_id'] = clerk_user_id
|
||||
created_event = await calendar_service.create_calendar_event(event_data, db)
|
||||
|
||||
return CalendarEventResponse(**created_event)
|
||||
@@ -54,11 +60,13 @@ async def create_calendar_event(
|
||||
@router.get("/", response_model=List[CalendarEventResponse])
|
||||
async def get_calendar_events(
|
||||
strategy_id: Optional[int] = Query(None, description="Filter by strategy ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get calendar events, optionally filtered by strategy."""
|
||||
try:
|
||||
logger.info("Fetching calendar events")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Fetching calendar events for user: {clerk_user_id}")
|
||||
|
||||
events = await calendar_service.get_calendar_events(strategy_id, db)
|
||||
return [CalendarEventResponse(**event) for event in events]
|
||||
@@ -70,11 +78,13 @@ async def get_calendar_events(
|
||||
@router.get("/{event_id}", response_model=CalendarEventResponse)
|
||||
async def get_calendar_event(
|
||||
event_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get a specific calendar event by ID."""
|
||||
try:
|
||||
logger.info(f"Fetching calendar event: {event_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Fetching calendar event: {event_id} for user: {clerk_user_id}")
|
||||
|
||||
event = await calendar_service.get_calendar_event_by_id(event_id, db)
|
||||
return CalendarEventResponse(**event)
|
||||
@@ -89,11 +99,13 @@ async def get_calendar_event(
|
||||
async def update_calendar_event(
|
||||
event_id: int,
|
||||
update_data: Dict[str, Any],
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update a calendar event."""
|
||||
try:
|
||||
logger.info(f"Updating calendar event: {event_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Updating calendar event: {event_id} for user: {clerk_user_id}")
|
||||
|
||||
updated_event = await calendar_service.update_calendar_event(event_id, update_data, db)
|
||||
return CalendarEventResponse(**updated_event)
|
||||
@@ -107,11 +119,13 @@ async def update_calendar_event(
|
||||
@router.delete("/{event_id}")
|
||||
async def delete_calendar_event(
|
||||
event_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Delete a calendar event."""
|
||||
try:
|
||||
logger.info(f"Deleting calendar event: {event_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Deleting calendar event: {event_id} for user: {clerk_user_id}")
|
||||
|
||||
deleted = await calendar_service.delete_calendar_event(event_id, db)
|
||||
|
||||
@@ -129,11 +143,13 @@ async def delete_calendar_event(
|
||||
@router.post("/schedule", response_model=Dict[str, Any])
|
||||
async def schedule_calendar_event(
|
||||
event: CalendarEventCreate,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Schedule a calendar event with conflict checking."""
|
||||
try:
|
||||
logger.info(f"Scheduling calendar event: {event.title}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Scheduling calendar event: {event.title} for user: {clerk_user_id}")
|
||||
|
||||
event_data = event.dict()
|
||||
result = await calendar_service.schedule_event(event_data, db)
|
||||
@@ -147,11 +163,13 @@ async def schedule_calendar_event(
|
||||
async def get_strategy_events(
|
||||
strategy_id: int,
|
||||
status: Optional[str] = Query(None, description="Filter by event status"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get calendar events for a specific strategy."""
|
||||
try:
|
||||
logger.info(f"Fetching events for strategy: {strategy_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Fetching events for strategy: {strategy_id} for user: {clerk_user_id}")
|
||||
|
||||
if status:
|
||||
events = await calendar_service.get_events_by_status(strategy_id, status, db)
|
||||
|
||||
@@ -114,25 +114,23 @@ async def generate_comprehensive_calendar(
|
||||
)
|
||||
|
||||
@router.post("/optimize-content", response_model=ContentOptimizationResponse)
|
||||
async def optimize_content_for_platform(request: ContentOptimizationRequest, db: Session = Depends(get_db)):
|
||||
async def optimize_content_for_platform(
|
||||
request: ContentOptimizationRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Optimize content for specific platforms using database insights.
|
||||
|
||||
This endpoint optimizes content based on:
|
||||
- Historical performance data for the platform
|
||||
- Audience preferences from onboarding data
|
||||
- Gap analysis insights for content improvement
|
||||
- Competitor analysis for differentiation
|
||||
- Active strategy data for optimal alignment
|
||||
Optimize content for specific platforms using database insights with user isolation.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"🔧 Starting content optimization for user {request.user_id}")
|
||||
clerk_user_id = str(current_user.get('id'))
|
||||
logger.info(f"🔧 Starting content optimization for authenticated user {clerk_user_id}")
|
||||
|
||||
# Initialize service with database session for active strategy access
|
||||
calendar_service = CalendarGenerationService(db)
|
||||
|
||||
result = await calendar_service.optimize_content_for_platform(
|
||||
user_id=request.user_id,
|
||||
user_id=clerk_user_id,
|
||||
title=request.title,
|
||||
description=request.description,
|
||||
content_type=request.content_type,
|
||||
@@ -152,24 +150,23 @@ async def optimize_content_for_platform(request: ContentOptimizationRequest, db:
|
||||
)
|
||||
|
||||
@router.post("/performance-predictions", response_model=PerformancePredictionResponse)
|
||||
async def predict_content_performance(request: PerformancePredictionRequest, db: Session = Depends(get_db)):
|
||||
async def predict_content_performance(
|
||||
request: PerformancePredictionRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Predict content performance using database insights.
|
||||
|
||||
This endpoint predicts performance based on:
|
||||
- Historical performance data
|
||||
- Audience demographics and preferences
|
||||
- Content type and platform patterns
|
||||
- Gap analysis opportunities
|
||||
Predict content performance using database insights with user isolation.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"📊 Starting performance prediction for user {request.user_id}")
|
||||
clerk_user_id = str(current_user.get('id'))
|
||||
logger.info(f"📊 Starting performance prediction for authenticated user {clerk_user_id}")
|
||||
|
||||
# Initialize service with database session for active strategy access
|
||||
calendar_service = CalendarGenerationService(db)
|
||||
|
||||
result = await calendar_service.predict_content_performance(
|
||||
user_id=request.user_id,
|
||||
user_id=clerk_user_id,
|
||||
content_type=request.content_type,
|
||||
platform=request.platform,
|
||||
content_data=request.content_data,
|
||||
@@ -186,24 +183,23 @@ async def predict_content_performance(request: PerformancePredictionRequest, db:
|
||||
)
|
||||
|
||||
@router.post("/repurpose-content", response_model=ContentRepurposingResponse)
|
||||
async def repurpose_content_across_platforms(request: ContentRepurposingRequest, db: Session = Depends(get_db)):
|
||||
async def repurpose_content_across_platforms(
|
||||
request: ContentRepurposingRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Repurpose content across different platforms using database insights.
|
||||
|
||||
This endpoint suggests content repurposing based on:
|
||||
- Existing content and strategy data
|
||||
- Gap analysis opportunities
|
||||
- Platform-specific requirements
|
||||
- Audience preferences
|
||||
Repurpose content across different platforms using database insights with user isolation.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"🔄 Starting content repurposing for user {request.user_id}")
|
||||
clerk_user_id = str(current_user.get('id'))
|
||||
logger.info(f"🔄 Starting content repurposing for authenticated user {clerk_user_id}")
|
||||
|
||||
# Initialize service with database session for active strategy access
|
||||
calendar_service = CalendarGenerationService(db)
|
||||
|
||||
result = await calendar_service.repurpose_content_across_platforms(
|
||||
user_id=request.user_id,
|
||||
user_id=clerk_user_id,
|
||||
original_content=request.original_content,
|
||||
target_platforms=request.target_platforms,
|
||||
strategy_id=request.strategy_id
|
||||
@@ -312,12 +308,16 @@ async def get_comprehensive_user_data(
|
||||
)
|
||||
|
||||
@router.get("/health")
|
||||
async def calendar_generation_health_check(db: Session = Depends(get_db)):
|
||||
async def calendar_generation_health_check(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Health check for calendar generation services.
|
||||
"""
|
||||
try:
|
||||
logger.info("🏥 Performing calendar generation health check")
|
||||
clerk_user_id = str(current_user.get('id'))
|
||||
logger.info(f"🏥 Performing calendar generation health check for user {clerk_user_id}")
|
||||
|
||||
# Initialize service with database session for active strategy access
|
||||
calendar_service = CalendarGenerationService(db)
|
||||
@@ -337,12 +337,17 @@ async def calendar_generation_health_check(db: Session = Depends(get_db)):
|
||||
}
|
||||
|
||||
@router.get("/progress/{session_id}")
|
||||
async def get_calendar_generation_progress(session_id: str, db: Session = Depends(get_db)):
|
||||
async def get_calendar_generation_progress(
|
||||
session_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get real-time progress of calendar generation for a specific session.
|
||||
This endpoint is polled by the frontend modal to show progress updates.
|
||||
"""
|
||||
try:
|
||||
clerk_user_id = str(current_user.get('id'))
|
||||
# Initialize service with database session for active strategy access
|
||||
calendar_service = CalendarGenerationService(db)
|
||||
|
||||
@@ -433,11 +438,16 @@ async def start_calendar_generation(
|
||||
raise HTTPException(status_code=500, detail="Failed to start calendar generation")
|
||||
|
||||
@router.delete("/cancel/{session_id}")
|
||||
async def cancel_calendar_generation(session_id: str, db: Session = Depends(get_db)):
|
||||
async def cancel_calendar_generation(
|
||||
session_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Cancel an ongoing calendar generation session.
|
||||
"""
|
||||
try:
|
||||
clerk_user_id = str(current_user.get('id'))
|
||||
# Initialize service with database session for active strategy access
|
||||
calendar_service = CalendarGenerationService(db)
|
||||
|
||||
@@ -463,9 +473,13 @@ async def cancel_calendar_generation(session_id: str, db: Session = Depends(get_
|
||||
|
||||
# Cache Management Endpoints
|
||||
@router.get("/cache/stats")
|
||||
async def get_cache_stats(db: Session = Depends(get_db)) -> Dict[str, Any]:
|
||||
async def get_cache_stats(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get comprehensive user data cache statistics."""
|
||||
try:
|
||||
clerk_user_id = str(current_user.get('id'))
|
||||
from services.comprehensive_user_data_cache_service import ComprehensiveUserDataCacheService
|
||||
cache_service = ComprehensiveUserDataCacheService(db)
|
||||
stats = cache_service.get_cache_stats()
|
||||
@@ -478,19 +492,21 @@ async def get_cache_stats(db: Session = Depends(get_db)) -> Dict[str, Any]:
|
||||
async def invalidate_user_cache(
|
||||
user_id: str,
|
||||
strategy_id: Optional[int] = Query(None, description="Strategy ID to invalidate (optional)"),
|
||||
db: Session = Depends(get_db)
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
) -> Dict[str, Any]:
|
||||
"""Invalidate cache for a specific user/strategy."""
|
||||
"""Invalidate cache for the authenticated user."""
|
||||
try:
|
||||
clerk_user_id = str(current_user.get('id'))
|
||||
from services.comprehensive_user_data_cache_service import ComprehensiveUserDataCacheService
|
||||
cache_service = ComprehensiveUserDataCacheService(db)
|
||||
success = cache_service.invalidate_cache(user_id, strategy_id)
|
||||
success = cache_service.invalidate_cache(clerk_user_id, strategy_id)
|
||||
|
||||
if success:
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Cache invalidated for user {user_id}" + (f" and strategy {strategy_id}" if strategy_id else ""),
|
||||
"user_id": user_id,
|
||||
"message": f"Cache invalidated for user {clerk_user_id}" + (f" and strategy {strategy_id}" if strategy_id else ""),
|
||||
"user_id": clerk_user_id,
|
||||
"strategy_id": strategy_id
|
||||
}
|
||||
else:
|
||||
@@ -501,9 +517,13 @@ async def invalidate_user_cache(
|
||||
raise HTTPException(status_code=500, detail="Failed to invalidate cache")
|
||||
|
||||
@router.post("/cache/cleanup")
|
||||
async def cleanup_expired_cache(db: Session = Depends(get_db)) -> Dict[str, Any]:
|
||||
async def cleanup_expired_cache(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
) -> Dict[str, Any]:
|
||||
"""Clean up expired cache entries."""
|
||||
try:
|
||||
clerk_user_id = str(current_user.get('id'))
|
||||
from services.comprehensive_user_data_cache_service import ComprehensiveUserDataCacheService
|
||||
cache_service = ComprehensiveUserDataCacheService(db)
|
||||
deleted_count = cache_service.cleanup_expired_cache()
|
||||
@@ -519,16 +539,22 @@ async def cleanup_expired_cache(db: Session = Depends(get_db)) -> Dict[str, Any]
|
||||
raise HTTPException(status_code=500, detail="Failed to clean up cache")
|
||||
|
||||
@router.get("/sessions")
|
||||
async def list_active_sessions(db: Session = Depends(get_db)):
|
||||
async def list_active_sessions(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
List all active calendar generation sessions.
|
||||
List active calendar generation sessions for the authenticated user.
|
||||
"""
|
||||
try:
|
||||
clerk_user_id = str(current_user.get('id'))
|
||||
# Initialize service with database session for active strategy access
|
||||
calendar_service = CalendarGenerationService(db)
|
||||
|
||||
sessions = []
|
||||
for session_id, session_data in calendar_service.orchestrator_sessions.items():
|
||||
if str(session_data.get("user_id", "")) != clerk_user_id:
|
||||
continue
|
||||
sessions.append({
|
||||
"session_id": session_id,
|
||||
"user_id": session_data.get("user_id"),
|
||||
@@ -548,11 +574,15 @@ async def list_active_sessions(db: Session = Depends(get_db)):
|
||||
raise HTTPException(status_code=500, detail="Failed to list sessions")
|
||||
|
||||
@router.delete("/sessions/cleanup")
|
||||
async def cleanup_old_sessions(db: Session = Depends(get_db)):
|
||||
async def cleanup_old_sessions(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Clean up old sessions.
|
||||
Clean up old sessions for the authenticated user.
|
||||
"""
|
||||
try:
|
||||
clerk_user_id = str(current_user.get('id'))
|
||||
# Initialize service with database session for active strategy access
|
||||
calendar_service = CalendarGenerationService(db)
|
||||
|
||||
|
||||
@@ -38,13 +38,16 @@ router = APIRouter(prefix="/gap-analysis", tags=["gap-analysis"])
|
||||
@router.post("/", response_model=ContentGapAnalysisResponse)
|
||||
async def create_content_gap_analysis(
|
||||
analysis: ContentGapAnalysisCreate,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create a new content gap analysis."""
|
||||
try:
|
||||
logger.info(f"Creating content gap analysis for: {analysis.website_url}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Creating content gap analysis for: {analysis.website_url} by user: {clerk_user_id}")
|
||||
|
||||
analysis_data = analysis.dict()
|
||||
analysis_data['user_id'] = clerk_user_id
|
||||
created_analysis = await gap_analysis_service.create_gap_analysis(analysis_data, db)
|
||||
|
||||
return ContentGapAnalysisResponse(**created_analysis)
|
||||
@@ -76,11 +79,13 @@ async def get_content_gap_analyses(
|
||||
@router.get("/{analysis_id}", response_model=ContentGapAnalysisResponse)
|
||||
async def get_content_gap_analysis(
|
||||
analysis_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get a specific content gap analysis by ID."""
|
||||
try:
|
||||
logger.info(f"Fetching content gap analysis: {analysis_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Fetching content gap analysis: {analysis_id} for user: {clerk_user_id}")
|
||||
|
||||
analysis = await gap_analysis_service.get_gap_analysis_by_id(analysis_id, db)
|
||||
return ContentGapAnalysisResponse(**analysis)
|
||||
@@ -117,15 +122,17 @@ async def analyze_content_gaps(
|
||||
@router.get("/user/{user_id}/analyses")
|
||||
async def get_user_gap_analyses(
|
||||
user_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get all gap analyses for a specific user."""
|
||||
"""Get all gap analyses for the authenticated user."""
|
||||
try:
|
||||
logger.info(f"Fetching gap analyses for user: {user_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Fetching gap analyses for authenticated user: {clerk_user_id}")
|
||||
|
||||
analyses = await gap_analysis_service.get_user_gap_analyses(user_id, db)
|
||||
analyses = await gap_analysis_service.get_user_gap_analyses(clerk_user_id, db)
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"user_id": clerk_user_id,
|
||||
"analyses": analyses,
|
||||
"total_count": len(analyses)
|
||||
}
|
||||
@@ -138,11 +145,13 @@ async def get_user_gap_analyses(
|
||||
async def update_content_gap_analysis(
|
||||
analysis_id: int,
|
||||
update_data: Dict[str, Any],
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update a content gap analysis."""
|
||||
try:
|
||||
logger.info(f"Updating content gap analysis: {analysis_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Updating content gap analysis: {analysis_id} for user: {clerk_user_id}")
|
||||
|
||||
updated_analysis = await gap_analysis_service.update_gap_analysis(analysis_id, update_data, db)
|
||||
return ContentGapAnalysisResponse(**updated_analysis)
|
||||
@@ -156,11 +165,13 @@ async def update_content_gap_analysis(
|
||||
@router.delete("/{analysis_id}")
|
||||
async def delete_content_gap_analysis(
|
||||
analysis_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Delete a content gap analysis."""
|
||||
try:
|
||||
logger.info(f"Deleting content gap analysis: {analysis_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Deleting content gap analysis: {analysis_id} for user: {clerk_user_id}")
|
||||
|
||||
deleted = await gap_analysis_service.delete_gap_analysis(analysis_id, db)
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
|
||||
# Import authentication
|
||||
from middleware.auth_middleware import get_current_user
|
||||
|
||||
# Import database service
|
||||
from services.database import get_db_session, get_db
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
@@ -28,7 +31,9 @@ ai_analysis_db_service = AIAnalysisDBService()
|
||||
router = APIRouter(prefix="/health", tags=["health-monitoring"])
|
||||
|
||||
@router.get("/backend", response_model=Dict[str, Any])
|
||||
async def check_backend_health():
|
||||
async def check_backend_health(
|
||||
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Check core backend health (independent of AI services)
|
||||
"""
|
||||
@@ -77,7 +82,9 @@ async def check_backend_health():
|
||||
}
|
||||
|
||||
@router.get("/ai", response_model=Dict[str, Any])
|
||||
async def check_ai_services_health():
|
||||
async def check_ai_services_health(
|
||||
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Check AI services health separately
|
||||
"""
|
||||
@@ -136,7 +143,10 @@ async def check_ai_services_health():
|
||||
}
|
||||
|
||||
@router.get("/database", response_model=Dict[str, Any])
|
||||
async def database_health_check(db: Session = Depends(get_db)):
|
||||
async def database_health_check(
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Health check for database operations.
|
||||
"""
|
||||
@@ -157,7 +167,10 @@ async def database_health_check(db: Session = Depends(get_db)):
|
||||
)
|
||||
|
||||
@router.get("/debug/strategies/{user_id}")
|
||||
async def debug_content_strategies(user_id: int):
|
||||
async def debug_content_strategies(
|
||||
user_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Debug endpoint to print content strategy data directly.
|
||||
"""
|
||||
@@ -203,7 +216,9 @@ async def debug_content_strategies(user_id: int):
|
||||
)
|
||||
|
||||
@router.get("/comprehensive", response_model=Dict[str, Any])
|
||||
async def comprehensive_health_check():
|
||||
async def comprehensive_health_check(
|
||||
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Comprehensive health check for all content planning services.
|
||||
"""
|
||||
|
||||
@@ -93,7 +93,10 @@ async def get_lightweight_statistics(current_user: Dict[str, Any] = Depends(get_
|
||||
}
|
||||
|
||||
@router.get("/cache-stats")
|
||||
async def get_cache_statistics(db = None) -> Dict[str, Any]:
|
||||
async def get_cache_statistics(
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Get comprehensive user data cache statistics."""
|
||||
try:
|
||||
if not db:
|
||||
|
||||
@@ -35,15 +35,18 @@ router = APIRouter(prefix="/strategies", tags=["strategies"])
|
||||
@router.post("/", response_model=ContentStrategyResponse)
|
||||
async def create_content_strategy(
|
||||
strategy: ContentStrategyCreate,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create a new content strategy."""
|
||||
try:
|
||||
logger.info(f"Creating content strategy: {strategy.name}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Creating content strategy: {strategy.name} for user: {clerk_user_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
strategy_service = EnhancedStrategyService(db_service)
|
||||
strategy_data = strategy.dict()
|
||||
strategy_data['user_id'] = clerk_user_id
|
||||
created_strategy = await strategy_service.create_enhanced_strategy(strategy_data, db)
|
||||
|
||||
return ContentStrategyResponse(**created_strategy)
|
||||
@@ -105,11 +108,13 @@ async def get_content_strategies(
|
||||
@router.get("/{strategy_id}", response_model=ContentStrategyResponse)
|
||||
async def get_content_strategy(
|
||||
strategy_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get a specific content strategy by ID."""
|
||||
try:
|
||||
logger.info(f"Fetching content strategy: {strategy_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Fetching content strategy: {strategy_id} for user: {clerk_user_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
strategy_service = EnhancedStrategyService(db_service)
|
||||
@@ -127,11 +132,13 @@ async def get_content_strategy(
|
||||
async def update_content_strategy(
|
||||
strategy_id: int,
|
||||
update_data: Dict[str, Any],
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update a content strategy."""
|
||||
try:
|
||||
logger.info(f"Updating content strategy: {strategy_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Updating content strategy: {strategy_id} for user: {clerk_user_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
updated_strategy = await db_service.update_enhanced_strategy(strategy_id, update_data)
|
||||
@@ -150,11 +157,13 @@ async def update_content_strategy(
|
||||
@router.delete("/{strategy_id}")
|
||||
async def delete_content_strategy(
|
||||
strategy_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Delete a content strategy."""
|
||||
try:
|
||||
logger.info(f"Deleting content strategy: {strategy_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Deleting content strategy: {strategy_id} for user: {clerk_user_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
deleted = await db_service.delete_enhanced_strategy(strategy_id)
|
||||
@@ -173,11 +182,13 @@ async def delete_content_strategy(
|
||||
@router.get("/{strategy_id}/analytics")
|
||||
async def get_strategy_analytics(
|
||||
strategy_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get analytics for a specific strategy."""
|
||||
try:
|
||||
logger.info(f"Fetching analytics for strategy: {strategy_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Fetching analytics for strategy: {strategy_id} for user: {clerk_user_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
analytics = await db_service.get_enhanced_strategies_with_analytics(strategy_id)
|
||||
@@ -194,11 +205,13 @@ async def get_strategy_analytics(
|
||||
@router.get("/{strategy_id}/summary")
|
||||
async def get_strategy_summary(
|
||||
strategy_id: int,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get a comprehensive summary of a strategy with analytics."""
|
||||
try:
|
||||
logger.info(f"Fetching summary for strategy: {strategy_id}")
|
||||
clerk_user_id = str(current_user.get('id', ''))
|
||||
logger.info(f"Fetching summary for strategy: {strategy_id} for user: {clerk_user_id}")
|
||||
|
||||
# Get strategy with analytics for comprehensive summary
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
"""
|
||||
Quality Validation Service
|
||||
AI response quality assessment and strategic analysis.
|
||||
All methods derive results from actual input data — no hardcoded defaults.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any, List
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
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.
|
||||
@@ -54,7 +55,10 @@ class QualityValidationService:
|
||||
_check(data, schema)
|
||||
|
||||
def calculate_strategic_scores(self, ai_recommendations: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""Calculate strategic performance scores from AI recommendations."""
|
||||
"""Calculate strategic performance scores from AI recommendations.
|
||||
Scores are derived per analysis type from actual metrics, then aggregated
|
||||
with dimension-specific weightings — no blanket multipliers.
|
||||
"""
|
||||
scores = {
|
||||
'overall_score': 0.0,
|
||||
'content_quality_score': 0.0,
|
||||
@@ -62,87 +66,214 @@ class QualityValidationService:
|
||||
'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
|
||||
|
||||
analysis_count = 0
|
||||
weighted_total = 0.0
|
||||
weight_sum = 0.0
|
||||
|
||||
# Dimension-specific weights
|
||||
dimension_weights = {
|
||||
'comprehensive_strategy': {'quality': 0.35, 'engagement': 0.20, 'conversion': 0.25, 'innovation': 0.20},
|
||||
'audience_intelligence': {'quality': 0.25, 'engagement': 0.40, 'conversion': 0.20, 'innovation': 0.15},
|
||||
'competitive_intelligence': {'quality': 0.30, 'engagement': 0.15, 'conversion': 0.25, 'innovation': 0.30},
|
||||
'performance_optimization': {'quality': 0.20, 'engagement': 0.15, 'conversion': 0.45, 'innovation': 0.20},
|
||||
'content_calendar_optimization': {'quality': 0.30, 'engagement': 0.25, 'conversion': 0.20, 'innovation': 0.25},
|
||||
}
|
||||
|
||||
|
||||
for analysis_type, recommendations in ai_recommendations.items():
|
||||
if not isinstance(recommendations, dict):
|
||||
continue
|
||||
metrics = recommendations.get('metrics')
|
||||
if not isinstance(metrics, dict):
|
||||
continue
|
||||
|
||||
score = metrics.get('score', 50)
|
||||
confidence = metrics.get('confidence', 0.5)
|
||||
weight = confidence
|
||||
|
||||
weighted_total += score * weight
|
||||
weight_sum += weight
|
||||
analysis_count += 1
|
||||
|
||||
weights = dimension_weights.get(analysis_type, {'quality': 0.25, 'engagement': 0.25, 'conversion': 0.25, 'innovation': 0.25})
|
||||
scores['content_quality_score'] += (score * weights['quality'] * weight)
|
||||
scores['engagement_score'] += (score * weights['engagement'] * weight)
|
||||
scores['conversion_score'] += (score * weights['conversion'] * weight)
|
||||
scores['innovation_score'] += (score * weights['innovation'] * weight)
|
||||
|
||||
if weight_sum > 0:
|
||||
scores['overall_score'] = round(weighted_total / weight_sum, 2)
|
||||
scores['content_quality_score'] = round(scores['content_quality_score'] / weight_sum, 2)
|
||||
scores['engagement_score'] = round(scores['engagement_score'] / weight_sum, 2)
|
||||
scores['conversion_score'] = round(scores['conversion_score'] / weight_sum, 2)
|
||||
scores['innovation_score'] = round(scores['innovation_score'] / weight_sum, 2)
|
||||
|
||||
return scores
|
||||
|
||||
def extract_market_positioning(self, ai_recommendations: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Extract market positioning from AI recommendations.
|
||||
Scans all analysis types for positioning, competitive_advantage, and market_share signals.
|
||||
Returns empty dict if no data is available instead of synthetic defaults.
|
||||
"""
|
||||
positioning = {}
|
||||
best_confidence = 0.0
|
||||
|
||||
for analysis_type, recommendations in ai_recommendations.items():
|
||||
if not isinstance(recommendations, dict):
|
||||
continue
|
||||
metrics = recommendations.get('metrics', {})
|
||||
confidence = metrics.get('confidence', 0.0)
|
||||
if confidence <= best_confidence:
|
||||
continue
|
||||
|
||||
recs = recommendations.get('recommendations', [])
|
||||
if isinstance(recs, list):
|
||||
for r in recs:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
pos = r.get('market_position') or r.get('positioning')
|
||||
adv = r.get('competitive_advantage')
|
||||
share = r.get('market_share')
|
||||
score = r.get('positioning_score') or metrics.get('positioning_score')
|
||||
if any([pos, adv, share, score]):
|
||||
best_confidence = confidence
|
||||
if pos:
|
||||
positioning['industry_position'] = pos
|
||||
if adv:
|
||||
positioning['competitive_advantage'] = adv
|
||||
if share:
|
||||
positioning['market_share'] = str(share)
|
||||
if score is not None:
|
||||
positioning['positioning_score'] = score
|
||||
|
||||
# Check top-level keys as fallback
|
||||
if not positioning:
|
||||
for key in ('industry_position', 'competitive_advantage', 'market_share', 'positioning_score'):
|
||||
val = ai_recommendations.get(key)
|
||||
if val is not None:
|
||||
positioning[key] = val
|
||||
|
||||
return positioning
|
||||
|
||||
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'
|
||||
}
|
||||
]
|
||||
|
||||
"""Extract competitive advantages from AI recommendations.
|
||||
Scans competitive_intelligence and other analysis types for advantage signals.
|
||||
Returns empty list if no data is available.
|
||||
"""
|
||||
advantages = []
|
||||
|
||||
for analysis_type, recommendations in ai_recommendations.items():
|
||||
if not isinstance(recommendations, dict):
|
||||
continue
|
||||
recs = recommendations.get('recommendations', [])
|
||||
if not isinstance(recs, list):
|
||||
continue
|
||||
for r in recs:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
adv = r.get('advantage') or r.get('competitive_advantage')
|
||||
if adv:
|
||||
advantages.append({
|
||||
'advantage': adv,
|
||||
'impact': r.get('impact', 'Medium'),
|
||||
'implementation': r.get('implementation', 'Planned')
|
||||
})
|
||||
|
||||
# Deduplicate by advantage text
|
||||
seen = set()
|
||||
unique = []
|
||||
for a in advantages:
|
||||
key = a['advantage'].strip().lower()
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
unique.append(a)
|
||||
|
||||
return unique
|
||||
|
||||
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'
|
||||
}
|
||||
]
|
||||
|
||||
"""Extract strategic risks from AI recommendations.
|
||||
Scans all analysis types for risk signals.
|
||||
Returns empty list if no data is available.
|
||||
"""
|
||||
risks = []
|
||||
|
||||
for analysis_type, recommendations in ai_recommendations.items():
|
||||
if not isinstance(recommendations, dict):
|
||||
continue
|
||||
recs = recommendations.get('recommendations', [])
|
||||
if not isinstance(recs, list):
|
||||
continue
|
||||
for r in recs:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
risk_text = r.get('risk') or r.get('strategic_risk') or r.get('threat')
|
||||
if risk_text:
|
||||
risks.append({
|
||||
'risk': risk_text,
|
||||
'probability': r.get('probability', 'Medium'),
|
||||
'impact': r.get('impact', 'Medium')
|
||||
})
|
||||
|
||||
risks_list = recommendations.get('risks') or recommendations.get('strategic_risks')
|
||||
if isinstance(risks_list, list):
|
||||
for r in risks_list:
|
||||
if isinstance(r, dict) and r.get('risk'):
|
||||
risks.append(r)
|
||||
|
||||
seen = set()
|
||||
unique = []
|
||||
for r in risks:
|
||||
key = r['risk'].strip().lower()
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
unique.append(r)
|
||||
|
||||
return unique
|
||||
|
||||
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'
|
||||
}
|
||||
]
|
||||
|
||||
"""Extract opportunity analysis from AI recommendations.
|
||||
Scans all analysis types for opportunity signals.
|
||||
Returns empty list if no data is available.
|
||||
"""
|
||||
opportunities = []
|
||||
|
||||
for analysis_type, recommendations in ai_recommendations.items():
|
||||
if not isinstance(recommendations, dict):
|
||||
continue
|
||||
recs = recommendations.get('recommendations', [])
|
||||
if not isinstance(recs, list):
|
||||
continue
|
||||
for r in recs:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
opp = r.get('opportunity') or r.get('growth_opportunity')
|
||||
if opp:
|
||||
opportunities.append({
|
||||
'opportunity': opp,
|
||||
'potential_impact': r.get('potential_impact', 'Medium'),
|
||||
'implementation_ease': r.get('implementation_ease', 'Medium')
|
||||
})
|
||||
|
||||
opps_list = recommendations.get('opportunities') or recommendations.get('growth_opportunities')
|
||||
if isinstance(opps_list, list):
|
||||
for o in opps_list:
|
||||
if isinstance(o, dict) and o.get('opportunity'):
|
||||
opportunities.append(o)
|
||||
|
||||
seen = set()
|
||||
unique = []
|
||||
for o in opportunities:
|
||||
key = o['opportunity'].strip().lower()
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
unique.append(o)
|
||||
|
||||
return unique
|
||||
|
||||
def validate_ai_response_quality(self, ai_response: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Validate the quality of AI response."""
|
||||
"""Validate the quality of AI response using multi-dimensional analysis.
|
||||
Scores are derived from actual content, not placeholders.
|
||||
"""
|
||||
quality_metrics = {
|
||||
'completeness': 0.0,
|
||||
'relevance': 0.0,
|
||||
@@ -150,30 +281,76 @@ class QualityValidationService:
|
||||
'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)
|
||||
|
||||
# Completeness: weighted by field importance
|
||||
field_weights = {
|
||||
'recommendations': 0.35,
|
||||
'insights': 0.30,
|
||||
'metrics': 0.20,
|
||||
'analysis_type': 0.15
|
||||
}
|
||||
weighted_present = 0.0
|
||||
total_weight = 0.0
|
||||
for field, weight in field_weights.items():
|
||||
total_weight += weight
|
||||
val = ai_response.get(field)
|
||||
if field == 'recommendations':
|
||||
if isinstance(val, list) and len(val) > 0:
|
||||
weighted_present += weight
|
||||
elif field == 'insights':
|
||||
if isinstance(val, list) and len(val) > 0:
|
||||
weighted_present += weight
|
||||
elif field == 'metrics':
|
||||
if isinstance(val, dict) and len(val) > 0:
|
||||
weighted_present += weight
|
||||
else:
|
||||
if val is not None:
|
||||
weighted_present += weight
|
||||
quality_metrics['completeness'] = round(weighted_present / total_weight, 2) if total_weight > 0 else 0.0
|
||||
|
||||
# Relevance: evaluate recommendations content quality
|
||||
recommendations = ai_response.get('recommendations', [])
|
||||
quality_metrics['actionability'] = min(1.0, len(recommendations) / 5.0)
|
||||
|
||||
# Calculate confidence
|
||||
if isinstance(recommendations, list) and len(recommendations) > 0:
|
||||
scored = 0
|
||||
total_recs = len(recommendations)
|
||||
for r in recommendations:
|
||||
if isinstance(r, dict):
|
||||
has_action = bool(r.get('action') or r.get('recommendation') or r.get('step'))
|
||||
has_reason = bool(r.get('reason') or r.get('rationale') or r.get('impact'))
|
||||
if has_action and has_reason:
|
||||
scored += 1
|
||||
quality_metrics['relevance'] = round(scored / total_recs, 2) if total_recs > 0 else 0.5
|
||||
else:
|
||||
quality_metrics['relevance'] = 0.0
|
||||
|
||||
# Actionability: recommendation detail score
|
||||
if isinstance(recommendations, list) and len(recommendations) > 0:
|
||||
actionable = 0
|
||||
for r in recommendations:
|
||||
if isinstance(r, dict):
|
||||
has_timeline = bool(r.get('timeline') or r.get('effort'))
|
||||
has_impact = bool(r.get('impact') or r.get('expected_outcome'))
|
||||
if has_timeline or has_impact:
|
||||
actionable += 1
|
||||
quality_metrics['actionability'] = round(min(1.0, actionable / max(len(recommendations), 1)), 2)
|
||||
else:
|
||||
quality_metrics['actionability'] = 0.0
|
||||
|
||||
# Confidence from metrics
|
||||
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)
|
||||
|
||||
quality_metrics['confidence'] = round(metrics.get('confidence', 0.0), 2) if isinstance(metrics, dict) else 0.0
|
||||
|
||||
# Overall weighted quality
|
||||
weights = {'completeness': 0.25, 'relevance': 0.30, 'actionability': 0.25, 'confidence': 0.20}
|
||||
overall = sum(quality_metrics[k] * weights[k] for k in weights)
|
||||
quality_metrics['overall_quality'] = round(overall, 2)
|
||||
|
||||
return quality_metrics
|
||||
|
||||
|
||||
def assess_strategy_quality(self, strategy_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Assess the overall quality of a content strategy."""
|
||||
"""Assess the overall quality of a content strategy.
|
||||
Uses field-level analysis with content-aware scoring — not simple presence checks.
|
||||
"""
|
||||
quality_assessment = {
|
||||
'data_completeness': 0.0,
|
||||
'strategic_clarity': 0.0,
|
||||
@@ -181,25 +358,59 @@ class QualityValidationService:
|
||||
'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)
|
||||
|
||||
|
||||
# Data completeness with weighted field groups
|
||||
field_groups = {
|
||||
'objectives': {'fields': ['business_objectives', 'target_metrics'], 'weight': 0.25},
|
||||
'resources': {'fields': ['content_budget', 'team_size', 'implementation_timeline'], 'weight': 0.25},
|
||||
'audience': {'fields': ['content_preferences', 'consumption_patterns', 'audience_pain_points'], 'weight': 0.25},
|
||||
'competition': {'fields': ['top_competitors', 'market_gaps', 'competitive_position'], 'weight': 0.25}
|
||||
}
|
||||
total_weight = 0.0
|
||||
weighted_score = 0.0
|
||||
for group_name, group in field_groups.items():
|
||||
group_present = sum(1 for f in group['fields'] if strategy_data.get(f) not in (None, '', []))
|
||||
group_score = group_present / len(group['fields']) if group['fields'] else 0
|
||||
weighted_score += group_score * group['weight']
|
||||
total_weight += group['weight']
|
||||
quality_assessment['data_completeness'] = round(weighted_score / total_weight, 2) if total_weight > 0 else 0.0
|
||||
|
||||
# Strategic clarity: evaluate quality of business objectives
|
||||
objectives = strategy_data.get('business_objectives')
|
||||
if isinstance(objectives, str) and len(objectives) > 20:
|
||||
quality_assessment['strategic_clarity'] = 0.9
|
||||
elif isinstance(objectives, str) and len(objectives) > 0:
|
||||
quality_assessment['strategic_clarity'] = 0.6
|
||||
elif isinstance(objectives, list) and len(objectives) > 0:
|
||||
quality_assessment['strategic_clarity'] = 0.8
|
||||
else:
|
||||
quality_assessment['strategic_clarity'] = 0.0
|
||||
|
||||
# Implementation readiness: budget + team + timeline
|
||||
readiness_signals = 0
|
||||
if strategy_data.get('content_budget') not in (None, '', 0):
|
||||
readiness_signals += 1
|
||||
if strategy_data.get('team_size') not in (None, '', 0):
|
||||
readiness_signals += 1
|
||||
if strategy_data.get('implementation_timeline') not in (None, '', []):
|
||||
readiness_signals += 1
|
||||
quality_assessment['implementation_readiness'] = round(readiness_signals / 3.0, 2)
|
||||
|
||||
# Competitive positioning: evaluate depth of competitive data
|
||||
comp_signals = 0
|
||||
if strategy_data.get('top_competitors') not in (None, '', []):
|
||||
comp_signals += 1
|
||||
if strategy_data.get('market_gaps') not in (None, '', []):
|
||||
comp_signals += 1
|
||||
if strategy_data.get('competitive_position') not in (None, ''):
|
||||
comp_signals += 1
|
||||
if strategy_data.get('industry_trends') not in (None, '', []):
|
||||
comp_signals += 1
|
||||
quality_assessment['competitive_positioning'] = round(comp_signals / 4.0, 2)
|
||||
|
||||
# Overall quality
|
||||
quality_assessment['overall_quality'] = round(
|
||||
sum(quality_assessment.values()) / len(quality_assessment), 2
|
||||
)
|
||||
|
||||
return quality_assessment
|
||||
@@ -510,7 +510,7 @@ class EnhancedStrategyService:
|
||||
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)
|
||||
return await self.health_monitoring_service.check_system_health(db)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting system health: {str(e)}")
|
||||
raise
|
||||
@@ -583,7 +583,7 @@ class EnhancedStrategyService:
|
||||
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(
|
||||
return await self.performance_optimization_service.optimize_response_time(
|
||||
operation_name, operation_func, *args, **kwargs
|
||||
)
|
||||
except Exception as e:
|
||||
|
||||
@@ -176,11 +176,7 @@ class FieldTransformationService:
|
||||
# Default transformation - use first available source data
|
||||
field_value = self._default_transformation(source_data, field_name)
|
||||
|
||||
# If no value found, provide default based on field type
|
||||
if field_value is None or field_value == "":
|
||||
field_value = self._get_default_value_for_field(field_name)
|
||||
|
||||
if field_value is not None:
|
||||
if field_value is not None and field_value != "":
|
||||
transformed_fields[field_name] = {
|
||||
'value': field_value,
|
||||
'source': sources[0] if sources else 'default',
|
||||
@@ -943,44 +939,6 @@ class FieldTransformationService:
|
||||
logger.error(f"Error extracting A/B testing capabilities: {str(e)}")
|
||||
return False
|
||||
|
||||
def _get_default_value_for_field(self, field_name: str) -> Any:
|
||||
"""Get default value for a field when no data is available."""
|
||||
# Provide sensible defaults for required fields
|
||||
default_values = {
|
||||
'business_objectives': 'Lead Generation, Brand Awareness',
|
||||
'target_metrics': 'Traffic Growth: 30%, Engagement Rate: 5%, Conversion Rate: 2%',
|
||||
'content_budget': 1000,
|
||||
'team_size': 1,
|
||||
'implementation_timeline': '3 months',
|
||||
'market_share': 'Small but growing',
|
||||
'competitive_position': 'Niche',
|
||||
'performance_metrics': 'Current Traffic: 1000, Current Engagement: 3%',
|
||||
'content_preferences': 'Blog posts, Social media content',
|
||||
'consumption_patterns': 'Mobile: 60%, Desktop: 40%',
|
||||
'audience_pain_points': 'Time constraints, Content quality',
|
||||
'buying_journey': 'Awareness: 40%, Consideration: 35%, Decision: 25%',
|
||||
'seasonal_trends': 'Q4 peak, Summer slowdown',
|
||||
'engagement_metrics': 'Likes: 100, Shares: 20, Comments: 15',
|
||||
'top_competitors': 'Competitor A, Competitor B',
|
||||
'competitor_content_strategies': 'Blog-focused, Video-heavy',
|
||||
'market_gaps': 'Underserved niche, Content gap',
|
||||
'industry_trends': 'AI integration, Video content',
|
||||
'emerging_trends': 'Voice search, Interactive content',
|
||||
'preferred_formats': ['Blog Posts', 'Videos', 'Infographics'],
|
||||
'content_mix': 'Educational: 40%, Entertaining: 30%, Promotional: 30%',
|
||||
'content_frequency': 'Weekly',
|
||||
'optimal_timing': 'Best Days: Tuesday, Thursday, Best Time: 10 AM',
|
||||
'quality_metrics': 'Readability: 8, Engagement: 7, SEO Score: 6',
|
||||
'editorial_guidelines': 'Professional tone, Clear structure',
|
||||
'brand_voice': 'Professional yet approachable',
|
||||
'traffic_sources': 'Organic: 60%, Social: 25%, Direct: 15%',
|
||||
'conversion_rates': 'Overall: 2%, Blog: 3%, Landing Pages: 5%',
|
||||
'content_roi_targets': 'Target ROI: 300%, Break Even: 6 months',
|
||||
'ab_testing_capabilities': False
|
||||
}
|
||||
|
||||
return default_values.get(field_name, None)
|
||||
|
||||
def _default_transformation(self, source_data: Dict[str, Any], field_name: str) -> Any:
|
||||
"""Default transformation when no specific method is available."""
|
||||
try:
|
||||
|
||||
@@ -44,6 +44,11 @@ class CachingService:
|
||||
'ttl': 900, # 15 minutes
|
||||
'max_size': 1000,
|
||||
'priority': 'low'
|
||||
},
|
||||
'streaming_intelligence': {
|
||||
'ttl': 300, # 5 minutes
|
||||
'max_size': 500,
|
||||
'priority': 'medium'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ from .data_processors import (
|
||||
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
|
||||
@@ -36,7 +35,6 @@ __all__ = [
|
||||
'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',
|
||||
|
||||
@@ -179,17 +179,13 @@ class DataProcessorService:
|
||||
}
|
||||
|
||||
fields['seasonal_trends'] = {
|
||||
'value': ['Q1: Planning', 'Q2: Execution', 'Q3: Optimization', 'Q4: Review'],
|
||||
'value': research_data.get('seasonal_trends', []),
|
||||
'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
|
||||
},
|
||||
'value': website_data.get('performance_metrics', {}),
|
||||
'source': 'website_analysis',
|
||||
'confidence': website_data.get('confidence_level', 0.8)
|
||||
}
|
||||
@@ -411,15 +407,6 @@ class DataProcessorService:
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -534,12 +521,6 @@ def get_detailed_input_data_points(processed_data: Dict[str, Any]) -> Dict[str,
|
||||
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()
|
||||
|
||||
@@ -14,6 +14,7 @@ logger = logging.getLogger(__name__)
|
||||
def calculate_strategic_scores(ai_recommendations: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""
|
||||
Calculate strategic performance scores from AI recommendations.
|
||||
Dimension-specific weights — no blanket multipliers.
|
||||
|
||||
Args:
|
||||
ai_recommendations: Dictionary containing AI analysis results
|
||||
@@ -28,35 +29,48 @@ def calculate_strategic_scores(ai_recommendations: Dict[str, Any]) -> Dict[str,
|
||||
'conversion_score': 0.0,
|
||||
'innovation_score': 0.0
|
||||
}
|
||||
|
||||
# Calculate scores based on AI recommendations
|
||||
total_confidence = 0
|
||||
total_score = 0
|
||||
|
||||
|
||||
weight_sum = 0.0
|
||||
|
||||
dimension_weights = {
|
||||
'comprehensive_strategy': {'quality': 0.35, 'engagement': 0.20, 'conversion': 0.25, 'innovation': 0.20},
|
||||
'audience_intelligence': {'quality': 0.25, 'engagement': 0.40, 'conversion': 0.20, 'innovation': 0.15},
|
||||
'competitive_intelligence': {'quality': 0.30, 'engagement': 0.15, 'conversion': 0.25, 'innovation': 0.30},
|
||||
'performance_optimization': {'quality': 0.20, 'engagement': 0.15, 'conversion': 0.45, 'innovation': 0.20},
|
||||
'content_calendar_optimization': {'quality': 0.30, 'engagement': 0.25, 'conversion': 0.20, 'innovation': 0.25},
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if not isinstance(recommendations, dict):
|
||||
continue
|
||||
metrics = recommendations.get('metrics')
|
||||
if not isinstance(metrics, dict):
|
||||
continue
|
||||
|
||||
score = metrics.get('score', 50)
|
||||
confidence = metrics.get('confidence', 0.5)
|
||||
weight = confidence
|
||||
|
||||
scores['overall_score'] += score * weight
|
||||
weight_sum += weight
|
||||
|
||||
weights = dimension_weights.get(analysis_type, {'quality': 0.25, 'engagement': 0.25, 'conversion': 0.25, 'innovation': 0.25})
|
||||
scores['content_quality_score'] += score * weights['quality'] * weight
|
||||
scores['engagement_score'] += score * weights['engagement'] * weight
|
||||
scores['conversion_score'] += score * weights['conversion'] * weight
|
||||
scores['innovation_score'] += score * weights['innovation'] * weight
|
||||
|
||||
if weight_sum > 0:
|
||||
for k in scores:
|
||||
scores[k] = round(scores[k] / weight_sum, 2)
|
||||
|
||||
return scores
|
||||
|
||||
|
||||
def extract_market_positioning(ai_recommendations: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract market positioning insights from AI recommendations.
|
||||
Scans all analysis types for positioning signals. Returns empty dict if none found.
|
||||
|
||||
Args:
|
||||
ai_recommendations: Dictionary containing AI analysis results
|
||||
@@ -64,17 +78,50 @@ def extract_market_positioning(ai_recommendations: Dict[str, Any]) -> Dict[str,
|
||||
Returns:
|
||||
Dictionary with market positioning data
|
||||
"""
|
||||
return {
|
||||
'industry_position': 'emerging',
|
||||
'competitive_advantage': 'AI-powered content',
|
||||
'market_share': '2.5%',
|
||||
'positioning_score': 4
|
||||
}
|
||||
positioning = {}
|
||||
best_confidence = 0.0
|
||||
|
||||
for analysis_type, recommendations in ai_recommendations.items():
|
||||
if not isinstance(recommendations, dict):
|
||||
continue
|
||||
metrics = recommendations.get('metrics', {})
|
||||
confidence = metrics.get('confidence', 0.0)
|
||||
if confidence <= best_confidence:
|
||||
continue
|
||||
|
||||
recs = recommendations.get('recommendations', [])
|
||||
if isinstance(recs, list):
|
||||
for r in recs:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
pos = r.get('market_position') or r.get('positioning')
|
||||
adv = r.get('competitive_advantage')
|
||||
share = r.get('market_share')
|
||||
score = r.get('positioning_score') or metrics.get('positioning_score')
|
||||
if any([pos, adv, share, score]):
|
||||
best_confidence = confidence
|
||||
if pos:
|
||||
positioning['industry_position'] = pos
|
||||
if adv:
|
||||
positioning['competitive_advantage'] = adv
|
||||
if share:
|
||||
positioning['market_share'] = str(share)
|
||||
if score is not None:
|
||||
positioning['positioning_score'] = score
|
||||
|
||||
if not positioning:
|
||||
for key in ('industry_position', 'competitive_advantage', 'market_share', 'positioning_score'):
|
||||
val = ai_recommendations.get(key)
|
||||
if val is not None:
|
||||
positioning[key] = val
|
||||
|
||||
return positioning
|
||||
|
||||
|
||||
def extract_competitive_advantages(ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Extract competitive advantages from AI recommendations.
|
||||
Scans all analysis types for advantage signals. Returns empty list if none found.
|
||||
|
||||
Args:
|
||||
ai_recommendations: Dictionary containing AI analysis results
|
||||
@@ -82,23 +129,40 @@ def extract_competitive_advantages(ai_recommendations: Dict[str, Any]) -> List[D
|
||||
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'
|
||||
}
|
||||
]
|
||||
advantages = []
|
||||
|
||||
for analysis_type, recommendations in ai_recommendations.items():
|
||||
if not isinstance(recommendations, dict):
|
||||
continue
|
||||
recs = recommendations.get('recommendations', [])
|
||||
if not isinstance(recs, list):
|
||||
continue
|
||||
for r in recs:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
adv = r.get('advantage') or r.get('competitive_advantage')
|
||||
if adv:
|
||||
advantages.append({
|
||||
'advantage': adv,
|
||||
'impact': r.get('impact', 'Medium'),
|
||||
'implementation': r.get('implementation', 'Planned')
|
||||
})
|
||||
|
||||
seen = set()
|
||||
unique = []
|
||||
for a in advantages:
|
||||
key = a['advantage'].strip().lower()
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
unique.append(a)
|
||||
|
||||
return unique
|
||||
|
||||
|
||||
def extract_strategic_risks(ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Extract strategic risks from AI recommendations.
|
||||
Scans all analysis types for risk signals. Returns empty list if none found.
|
||||
|
||||
Args:
|
||||
ai_recommendations: Dictionary containing AI analysis results
|
||||
@@ -106,23 +170,46 @@ def extract_strategic_risks(ai_recommendations: Dict[str, Any]) -> List[Dict[str
|
||||
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'
|
||||
}
|
||||
]
|
||||
risks = []
|
||||
|
||||
for analysis_type, recommendations in ai_recommendations.items():
|
||||
if not isinstance(recommendations, dict):
|
||||
continue
|
||||
recs = recommendations.get('recommendations', [])
|
||||
if not isinstance(recs, list):
|
||||
continue
|
||||
for r in recs:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
risk_text = r.get('risk') or r.get('strategic_risk') or r.get('threat')
|
||||
if risk_text:
|
||||
risks.append({
|
||||
'risk': risk_text,
|
||||
'probability': r.get('probability', 'Medium'),
|
||||
'impact': r.get('impact', 'Medium')
|
||||
})
|
||||
|
||||
risks_list = recommendations.get('risks') or recommendations.get('strategic_risks')
|
||||
if isinstance(risks_list, list):
|
||||
for r in risks_list:
|
||||
if isinstance(r, dict) and r.get('risk'):
|
||||
risks.append(r)
|
||||
|
||||
seen = set()
|
||||
unique = []
|
||||
for r in risks:
|
||||
key = r['risk'].strip().lower()
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
unique.append(r)
|
||||
|
||||
return unique
|
||||
|
||||
|
||||
def extract_opportunity_analysis(ai_recommendations: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Extract opportunity analysis from AI recommendations.
|
||||
Scans all analysis types for opportunity signals. Returns empty list if none found.
|
||||
|
||||
Args:
|
||||
ai_recommendations: Dictionary containing AI analysis results
|
||||
@@ -130,18 +217,40 @@ def extract_opportunity_analysis(ai_recommendations: Dict[str, Any]) -> List[Dic
|
||||
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'
|
||||
}
|
||||
]
|
||||
opportunities = []
|
||||
|
||||
for analysis_type, recommendations in ai_recommendations.items():
|
||||
if not isinstance(recommendations, dict):
|
||||
continue
|
||||
recs = recommendations.get('recommendations', [])
|
||||
if not isinstance(recs, list):
|
||||
continue
|
||||
for r in recs:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
opp = r.get('opportunity') or r.get('growth_opportunity')
|
||||
if opp:
|
||||
opportunities.append({
|
||||
'opportunity': opp,
|
||||
'potential_impact': r.get('potential_impact', 'Medium'),
|
||||
'implementation_ease': r.get('implementation_ease', 'Medium')
|
||||
})
|
||||
|
||||
opps_list = recommendations.get('opportunities') or recommendations.get('growth_opportunities')
|
||||
if isinstance(opps_list, list):
|
||||
for o in opps_list:
|
||||
if isinstance(o, dict) and o.get('opportunity'):
|
||||
opportunities.append(o)
|
||||
|
||||
seen = set()
|
||||
unique = []
|
||||
for o in opportunities:
|
||||
key = o['opportunity'].strip().lower()
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
unique.append(o)
|
||||
|
||||
return unique
|
||||
|
||||
|
||||
def initialize_caches() -> Dict[str, Any]:
|
||||
|
||||
@@ -192,10 +192,6 @@ class EnhancedStrategyService:
|
||||
"""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)
|
||||
@@ -220,22 +216,6 @@ class EnhancedStrategyService:
|
||||
"""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
|
||||
|
||||
Reference in New Issue
Block a user