ALwrity version 0.5.5

This commit is contained in:
ajaysi
2025-08-19 21:48:33 +05:30
parent 5f104bf427
commit 74e22b421a
97 changed files with 16770 additions and 5000 deletions

View File

@@ -1,4 +1,3 @@
# 🏗️ Content Planning Services Modularity & Optimization Plan
## 📋 Executive Summary

View File

@@ -10,8 +10,8 @@ import json
import re
# Import AI providers
from llm_providers.main_text_generation import llm_text_gen
from llm_providers.gemini_provider import gemini_structured_json_response
from services.llm_providers.main_text_generation import llm_text_gen
from services.llm_providers.gemini_provider import gemini_structured_json_response
class AIPromptOptimizer:
"""Advanced AI prompt optimization and management service."""
@@ -299,8 +299,19 @@ Format as structured JSON with detailed metrics and strategic recommendations.
schema=self.schemas['strategic_content_gap_analysis']
)
# Parse and return the AI response
result = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
result = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
result = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
logger.info("✅ Advanced strategic content gap analysis completed")
return result
@@ -336,8 +347,19 @@ Format as structured JSON with detailed metrics and strategic recommendations.
schema=self.schemas['market_position_analysis']
)
# Parse and return the AI response
result = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
result = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
result = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
logger.info("✅ Advanced market position analysis completed")
return result
@@ -373,8 +395,19 @@ Format as structured JSON with detailed metrics and strategic recommendations.
schema=self.schemas['advanced_keyword_analysis']
)
# Parse and return the AI response
result = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
result = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
result = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
logger.info("✅ Advanced keyword analysis completed")
return result

View File

@@ -0,0 +1,611 @@
"""
AI Quality Analysis Service
Provides AI-powered quality assessment and recommendations for content strategies.
"""
import logging
import asyncio
from typing import Dict, Any, List, Optional
from datetime import datetime, timedelta
from dataclasses import dataclass
from enum import Enum
from services.llm_providers.gemini_provider import gemini_structured_json_response
from services.strategy_service import StrategyService
from models.enhanced_strategy_models import EnhancedContentStrategy
logger = logging.getLogger(__name__)
class QualityScore(Enum):
EXCELLENT = "excellent"
GOOD = "good"
NEEDS_ATTENTION = "needs_attention"
POOR = "poor"
@dataclass
class QualityMetric:
name: str
score: float # 0-100
weight: float # 0-1
status: QualityScore
description: str
recommendations: List[str]
@dataclass
class QualityAnalysisResult:
overall_score: float
overall_status: QualityScore
metrics: List[QualityMetric]
recommendations: List[str]
confidence_score: float
analysis_timestamp: datetime
strategy_id: int
# Structured JSON schemas for Gemini API
QUALITY_ANALYSIS_SCHEMA = {
"type": "OBJECT",
"properties": {
"score": {"type": "NUMBER"},
"status": {"type": "STRING"},
"description": {"type": "STRING"},
"recommendations": {
"type": "ARRAY",
"items": {"type": "STRING"}
}
},
"propertyOrdering": ["score", "status", "description", "recommendations"]
}
RECOMMENDATIONS_SCHEMA = {
"type": "OBJECT",
"properties": {
"recommendations": {
"type": "ARRAY",
"items": {"type": "STRING"}
},
"priority_areas": {
"type": "ARRAY",
"items": {"type": "STRING"}
}
},
"propertyOrdering": ["recommendations", "priority_areas"]
}
class AIQualityAnalysisService:
"""AI-powered quality assessment service for content strategies."""
def __init__(self):
self.strategy_service = StrategyService()
async def analyze_strategy_quality(self, strategy_id: int) -> QualityAnalysisResult:
"""Analyze strategy quality using AI and return comprehensive results."""
try:
logger.info(f"Starting AI quality analysis for strategy {strategy_id}")
# Get strategy data
strategy_data = await self.strategy_service.get_strategy_by_id(strategy_id)
if not strategy_data:
raise ValueError(f"Strategy {strategy_id} not found")
# Perform comprehensive quality analysis
quality_metrics = await self._analyze_quality_metrics(strategy_data)
# Calculate overall score
overall_score = self._calculate_overall_score(quality_metrics)
overall_status = self._determine_overall_status(overall_score)
# Generate AI recommendations
recommendations = await self._generate_ai_recommendations(strategy_data, quality_metrics)
# Calculate confidence score
confidence_score = self._calculate_confidence_score(quality_metrics)
result = QualityAnalysisResult(
overall_score=overall_score,
overall_status=overall_status,
metrics=quality_metrics,
recommendations=recommendations,
confidence_score=confidence_score,
analysis_timestamp=datetime.utcnow(),
strategy_id=strategy_id
)
# Save analysis result to database
await self._save_quality_analysis(result)
logger.info(f"Quality analysis completed for strategy {strategy_id}. Score: {overall_score}")
return result
except Exception as e:
logger.error(f"Error analyzing strategy quality for {strategy_id}: {e}")
raise
async def _analyze_quality_metrics(self, strategy_data: Dict[str, Any]) -> List[QualityMetric]:
"""Analyze individual quality metrics for a strategy."""
metrics = []
# 1. Strategic Completeness Analysis
completeness_metric = await self._analyze_strategic_completeness(strategy_data)
metrics.append(completeness_metric)
# 2. Audience Intelligence Quality
audience_metric = await self._analyze_audience_intelligence(strategy_data)
metrics.append(audience_metric)
# 3. Competitive Intelligence Quality
competitive_metric = await self._analyze_competitive_intelligence(strategy_data)
metrics.append(competitive_metric)
# 4. Content Strategy Quality
content_metric = await self._analyze_content_strategy(strategy_data)
metrics.append(content_metric)
# 5. Performance Alignment Quality
performance_metric = await self._analyze_performance_alignment(strategy_data)
metrics.append(performance_metric)
# 6. Implementation Feasibility
feasibility_metric = await self._analyze_implementation_feasibility(strategy_data)
metrics.append(feasibility_metric)
return metrics
async def _analyze_strategic_completeness(self, strategy_data: Dict[str, Any]) -> QualityMetric:
"""Analyze strategic completeness and depth."""
try:
# Check required fields
required_fields = [
'business_objectives', 'target_metrics', 'content_budget',
'team_size', 'implementation_timeline', 'market_share'
]
filled_fields = sum(1 for field in required_fields if strategy_data.get(field))
completeness_score = (filled_fields / len(required_fields)) * 100
# AI analysis of strategic depth
prompt = f"""
Analyze the strategic completeness of this content strategy:
Business Objectives: {strategy_data.get('business_objectives', 'Not provided')}
Target Metrics: {strategy_data.get('target_metrics', 'Not provided')}
Content Budget: {strategy_data.get('content_budget', 'Not provided')}
Team Size: {strategy_data.get('team_size', 'Not provided')}
Implementation Timeline: {strategy_data.get('implementation_timeline', 'Not provided')}
Market Share: {strategy_data.get('market_share', 'Not provided')}
Provide a quality score (0-100), status (excellent/good/needs_attention/poor), description, and specific recommendations for improvement.
Focus on strategic depth, clarity, and measurability.
"""
ai_response = await gemini_structured_json_response(
prompt=prompt,
schema=QUALITY_ANALYSIS_SCHEMA,
temperature=0.3,
max_tokens=2048
)
if "error" in ai_response:
raise ValueError(f"AI analysis failed: {ai_response['error']}")
# Parse AI response
ai_score = ai_response.get('score', 60.0)
ai_status = ai_response.get('status', 'needs_attention')
description = ai_response.get('description', 'Strategic completeness analysis')
recommendations = ai_response.get('recommendations', [])
# Combine manual and AI scores
final_score = (completeness_score * 0.4) + (ai_score * 0.6)
return QualityMetric(
name="Strategic Completeness",
score=final_score,
weight=0.25,
status=self._parse_status(ai_status),
description=description,
recommendations=recommendations
)
except Exception as e:
logger.error(f"Error analyzing strategic completeness: {e}")
raise ValueError(f"Failed to analyze strategic completeness: {str(e)}")
async def _analyze_audience_intelligence(self, strategy_data: Dict[str, Any]) -> QualityMetric:
"""Analyze audience intelligence quality."""
try:
audience_fields = [
'content_preferences', 'consumption_patterns', 'audience_pain_points',
'buying_journey', 'seasonal_trends', 'engagement_metrics'
]
filled_fields = sum(1 for field in audience_fields if strategy_data.get(field))
completeness_score = (filled_fields / len(audience_fields)) * 100
# AI analysis of audience insights
prompt = f"""
Analyze the audience intelligence quality of this content strategy:
Content Preferences: {strategy_data.get('content_preferences', 'Not provided')}
Consumption Patterns: {strategy_data.get('consumption_patterns', 'Not provided')}
Audience Pain Points: {strategy_data.get('audience_pain_points', 'Not provided')}
Buying Journey: {strategy_data.get('buying_journey', 'Not provided')}
Seasonal Trends: {strategy_data.get('seasonal_trends', 'Not provided')}
Engagement Metrics: {strategy_data.get('engagement_metrics', 'Not provided')}
Provide a quality score (0-100), status (excellent/good/needs_attention/poor), description, and specific recommendations for improvement.
Focus on audience understanding, segmentation, and actionable insights.
"""
ai_response = await gemini_structured_json_response(
prompt=prompt,
schema=QUALITY_ANALYSIS_SCHEMA,
temperature=0.3,
max_tokens=2048
)
if "error" in ai_response:
raise ValueError(f"AI analysis failed: {ai_response['error']}")
ai_score = ai_response.get('score', 60.0)
ai_status = ai_response.get('status', 'needs_attention')
description = ai_response.get('description', 'Audience intelligence analysis')
recommendations = ai_response.get('recommendations', [])
final_score = (completeness_score * 0.3) + (ai_score * 0.7)
return QualityMetric(
name="Audience Intelligence",
score=final_score,
weight=0.20,
status=self._parse_status(ai_status),
description=description,
recommendations=recommendations
)
except Exception as e:
logger.error(f"Error analyzing audience intelligence: {e}")
raise ValueError(f"Failed to analyze audience intelligence: {str(e)}")
async def _analyze_competitive_intelligence(self, strategy_data: Dict[str, Any]) -> QualityMetric:
"""Analyze competitive intelligence quality."""
try:
competitive_fields = [
'top_competitors', 'competitor_content_strategies', 'market_gaps',
'industry_trends', 'emerging_trends'
]
filled_fields = sum(1 for field in competitive_fields if strategy_data.get(field))
completeness_score = (filled_fields / len(competitive_fields)) * 100
# AI analysis of competitive insights
prompt = f"""
Analyze the competitive intelligence quality of this content strategy:
Top Competitors: {strategy_data.get('top_competitors', 'Not provided')}
Competitor Content Strategies: {strategy_data.get('competitor_content_strategies', 'Not provided')}
Market Gaps: {strategy_data.get('market_gaps', 'Not provided')}
Industry Trends: {strategy_data.get('industry_trends', 'Not provided')}
Emerging Trends: {strategy_data.get('emerging_trends', 'Not provided')}
Provide a quality score (0-100), status (excellent/good/needs_attention/poor), description, and specific recommendations for improvement.
Focus on competitive positioning, differentiation opportunities, and market insights.
"""
ai_response = await gemini_structured_json_response(
prompt=prompt,
schema=QUALITY_ANALYSIS_SCHEMA,
temperature=0.3,
max_tokens=2048
)
if "error" in ai_response:
raise ValueError(f"AI analysis failed: {ai_response['error']}")
ai_score = ai_response.get('score', 60.0)
ai_status = ai_response.get('status', 'needs_attention')
description = ai_response.get('description', 'Competitive intelligence analysis')
recommendations = ai_response.get('recommendations', [])
final_score = (completeness_score * 0.3) + (ai_score * 0.7)
return QualityMetric(
name="Competitive Intelligence",
score=final_score,
weight=0.15,
status=self._parse_status(ai_status),
description=description,
recommendations=recommendations
)
except Exception as e:
logger.error(f"Error analyzing competitive intelligence: {e}")
raise ValueError(f"Failed to analyze competitive intelligence: {str(e)}")
async def _analyze_content_strategy(self, strategy_data: Dict[str, Any]) -> QualityMetric:
"""Analyze content strategy quality."""
try:
content_fields = [
'preferred_formats', 'content_mix', 'content_frequency',
'optimal_timing', 'quality_metrics', 'editorial_guidelines', 'brand_voice'
]
filled_fields = sum(1 for field in content_fields if strategy_data.get(field))
completeness_score = (filled_fields / len(content_fields)) * 100
# AI analysis of content strategy
prompt = f"""
Analyze the content strategy quality:
Preferred Formats: {strategy_data.get('preferred_formats', 'Not provided')}
Content Mix: {strategy_data.get('content_mix', 'Not provided')}
Content Frequency: {strategy_data.get('content_frequency', 'Not provided')}
Optimal Timing: {strategy_data.get('optimal_timing', 'Not provided')}
Quality Metrics: {strategy_data.get('quality_metrics', 'Not provided')}
Editorial Guidelines: {strategy_data.get('editorial_guidelines', 'Not provided')}
Brand Voice: {strategy_data.get('brand_voice', 'Not provided')}
Provide a quality score (0-100), status (excellent/good/needs_attention/poor), description, and specific recommendations for improvement.
Focus on content planning, execution strategy, and quality standards.
"""
ai_response = await gemini_structured_json_response(
prompt=prompt,
schema=QUALITY_ANALYSIS_SCHEMA,
temperature=0.3,
max_tokens=2048
)
if "error" in ai_response:
raise ValueError(f"AI analysis failed: {ai_response['error']}")
ai_score = ai_response.get('score', 60.0)
ai_status = ai_response.get('status', 'needs_attention')
description = ai_response.get('description', 'Content strategy analysis')
recommendations = ai_response.get('recommendations', [])
final_score = (completeness_score * 0.3) + (ai_score * 0.7)
return QualityMetric(
name="Content Strategy",
score=final_score,
weight=0.20,
status=self._parse_status(ai_status),
description=description,
recommendations=recommendations
)
except Exception as e:
logger.error(f"Error analyzing content strategy: {e}")
raise ValueError(f"Failed to analyze content strategy: {str(e)}")
async def _analyze_performance_alignment(self, strategy_data: Dict[str, Any]) -> QualityMetric:
"""Analyze performance alignment quality."""
try:
performance_fields = [
'traffic_sources', 'conversion_rates', 'content_roi_targets',
'ab_testing_capabilities'
]
filled_fields = sum(1 for field in performance_fields if strategy_data.get(field))
completeness_score = (filled_fields / len(performance_fields)) * 100
# AI analysis of performance alignment
prompt = f"""
Analyze the performance alignment quality:
Traffic Sources: {strategy_data.get('traffic_sources', 'Not provided')}
Conversion Rates: {strategy_data.get('conversion_rates', 'Not provided')}
Content ROI Targets: {strategy_data.get('content_roi_targets', 'Not provided')}
A/B Testing Capabilities: {strategy_data.get('ab_testing_capabilities', 'Not provided')}
Provide a quality score (0-100), status (excellent/good/needs_attention/poor), description, and specific recommendations for improvement.
Focus on performance measurement, optimization, and ROI alignment.
"""
ai_response = await gemini_structured_json_response(
prompt=prompt,
schema=QUALITY_ANALYSIS_SCHEMA,
temperature=0.3,
max_tokens=2048
)
if "error" in ai_response:
raise ValueError(f"AI analysis failed: {ai_response['error']}")
ai_score = ai_response.get('score', 60.0)
ai_status = ai_response.get('status', 'needs_attention')
description = ai_response.get('description', 'Performance alignment analysis')
recommendations = ai_response.get('recommendations', [])
final_score = (completeness_score * 0.3) + (ai_score * 0.7)
return QualityMetric(
name="Performance Alignment",
score=final_score,
weight=0.15,
status=self._parse_status(ai_status),
description=description,
recommendations=recommendations
)
except Exception as e:
logger.error(f"Error analyzing performance alignment: {e}")
raise ValueError(f"Failed to analyze performance alignment: {str(e)}")
async def _analyze_implementation_feasibility(self, strategy_data: Dict[str, Any]) -> QualityMetric:
"""Analyze implementation feasibility."""
try:
# Check resource availability
has_budget = bool(strategy_data.get('content_budget'))
has_team = bool(strategy_data.get('team_size'))
has_timeline = bool(strategy_data.get('implementation_timeline'))
resource_score = ((has_budget + has_team + has_timeline) / 3) * 100
# AI analysis of feasibility
prompt = f"""
Analyze the implementation feasibility of this content strategy:
Content Budget: {strategy_data.get('content_budget', 'Not provided')}
Team Size: {strategy_data.get('team_size', 'Not provided')}
Implementation Timeline: {strategy_data.get('implementation_timeline', 'Not provided')}
Industry: {strategy_data.get('industry', 'Not provided')}
Market Share: {strategy_data.get('market_share', 'Not provided')}
Provide a quality score (0-100), status (excellent/good/needs_attention/poor), description, and specific recommendations for improvement.
Focus on resource availability, timeline feasibility, and implementation challenges.
"""
ai_response = await gemini_structured_json_response(
prompt=prompt,
schema=QUALITY_ANALYSIS_SCHEMA,
temperature=0.3,
max_tokens=2048
)
if "error" in ai_response:
raise ValueError(f"AI analysis failed: {ai_response['error']}")
ai_score = ai_response.get('score', 60.0)
ai_status = ai_response.get('status', 'needs_attention')
description = ai_response.get('description', 'Implementation feasibility analysis')
recommendations = ai_response.get('recommendations', [])
final_score = (resource_score * 0.4) + (ai_score * 0.6)
return QualityMetric(
name="Implementation Feasibility",
score=final_score,
weight=0.05,
status=self._parse_status(ai_status),
description=description,
recommendations=recommendations
)
except Exception as e:
logger.error(f"Error analyzing implementation feasibility: {e}")
raise ValueError(f"Failed to analyze implementation feasibility: {str(e)}")
def _calculate_overall_score(self, metrics: List[QualityMetric]) -> float:
"""Calculate weighted overall quality score."""
if not metrics:
return 0.0
weighted_sum = sum(metric.score * metric.weight for metric in metrics)
total_weight = sum(metric.weight for metric in metrics)
return weighted_sum / total_weight if total_weight > 0 else 0.0
def _determine_overall_status(self, score: float) -> QualityScore:
"""Determine overall quality status based on score."""
if score >= 85:
return QualityScore.EXCELLENT
elif score >= 70:
return QualityScore.GOOD
elif score >= 50:
return QualityScore.NEEDS_ATTENTION
else:
return QualityScore.POOR
def _parse_status(self, status_str: str) -> QualityScore:
"""Parse status string to QualityScore enum."""
status_lower = status_str.lower()
if status_lower == 'excellent':
return QualityScore.EXCELLENT
elif status_lower == 'good':
return QualityScore.GOOD
elif status_lower == 'needs_attention':
return QualityScore.NEEDS_ATTENTION
elif status_lower == 'poor':
return QualityScore.POOR
else:
return QualityScore.NEEDS_ATTENTION
async def _generate_ai_recommendations(self, strategy_data: Dict[str, Any], metrics: List[QualityMetric]) -> List[str]:
"""Generate AI-powered recommendations for strategy improvement."""
try:
# Identify areas needing improvement
low_metrics = [m for m in metrics if m.status in [QualityScore.NEEDS_ATTENTION, QualityScore.POOR]]
if not low_metrics:
return ["Strategy quality is excellent. Continue monitoring and optimizing based on performance data."]
# Generate specific recommendations
prompt = f"""
Based on the quality analysis of this content strategy, provide 3-5 specific, actionable recommendations for improvement.
Strategy Overview:
- Industry: {strategy_data.get('industry', 'Not specified')}
- Business Objectives: {strategy_data.get('business_objectives', 'Not specified')}
Areas needing improvement:
{chr(10).join([f"- {m.name}: {m.score:.1f}/100" for m in low_metrics])}
Provide specific, actionable recommendations that can be implemented immediately.
Focus on the most impactful improvements first.
"""
ai_response = await gemini_structured_json_response(
prompt=prompt,
schema=RECOMMENDATIONS_SCHEMA,
temperature=0.3,
max_tokens=2048
)
if "error" in ai_response:
raise ValueError(f"AI recommendations failed: {ai_response['error']}")
recommendations = ai_response.get('recommendations', [])
return recommendations[:5] # Limit to 5 recommendations
except Exception as e:
logger.error(f"Error generating AI recommendations: {e}")
raise ValueError(f"Failed to generate AI recommendations: {str(e)}")
def _calculate_confidence_score(self, metrics: List[QualityMetric]) -> float:
"""Calculate confidence score based on data quality and analysis depth."""
if not metrics:
return 0.0
# Higher scores indicate more confidence
avg_score = sum(m.score for m in metrics) / len(metrics)
# More metrics analyzed = higher confidence
metric_count_factor = min(len(metrics) / 6, 1.0) # 6 is max expected metrics
confidence = (avg_score * 0.7) + (metric_count_factor * 100 * 0.3)
return min(confidence, 100.0)
async def _save_quality_analysis(self, result: QualityAnalysisResult) -> bool:
"""Save quality analysis result to database."""
try:
# This would save to a quality_analysis_results table
# For now, we'll log the result
logger.info(f"Quality analysis saved for strategy {result.strategy_id}")
return True
except Exception as e:
logger.error(f"Error saving quality analysis: {e}")
return False
async def get_quality_history(self, strategy_id: int, days: int = 30) -> List[QualityAnalysisResult]:
"""Get quality analysis history for a strategy."""
try:
# This would query the quality_analysis_results table
# For now, return empty list
return []
except Exception as e:
logger.error(f"Error getting quality history: {e}")
return []
async def get_quality_trends(self, strategy_id: int) -> Dict[str, Any]:
"""Get quality trends over time."""
try:
# This would analyze quality trends over time
# For now, return empty data
return {
"trend": "stable",
"change_rate": 0,
"consistency_score": 0
}
except Exception as e:
logger.error(f"Error getting quality trends: {e}")
return {"trend": "stable", "change_rate": 0, "consistency_score": 0}

View File

@@ -12,13 +12,13 @@ from dataclasses import dataclass
from enum import Enum
# Import AI providers
from llm_providers.main_text_generation import llm_text_gen
from services.llm_providers.main_text_generation import llm_text_gen
# Prefer the extended gemini provider if available; fallback to base
try:
from services.llm_providers.gemini_provider import gemini_structured_json_response as _gemini_fn
_GEMINI_EXTENDED = True
except Exception:
from llm_providers.gemini_provider import gemini_structured_json_response as _gemini_fn
from services.llm_providers.gemini_provider import gemini_structured_json_response as _gemini_fn
_GEMINI_EXTENDED = False
class AIServiceType(Enum):

View File

@@ -12,8 +12,8 @@ import json
from collections import Counter, defaultdict
# Import AI providers
from llm_providers.main_text_generation import llm_text_gen
from llm_providers.gemini_provider import gemini_structured_json_response
from services.llm_providers.main_text_generation import llm_text_gen
from services.llm_providers.gemini_provider import gemini_structured_json_response
# Import services
from services.ai_service_manager import AIServiceManager
@@ -213,8 +213,19 @@ class AIEngineService:
}
)
# Parse and return the AI response
result = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
result = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
result = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
recommendations = result.get('recommendations', [])
logger.info(f"✅ Generated {len(recommendations)} AI content recommendations")
return recommendations
@@ -355,8 +366,19 @@ class AIEngineService:
}
)
# Parse and return the AI response
predictions = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
predictions = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
predictions = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
logger.info("✅ AI performance predictions completed")
return predictions
@@ -495,7 +517,19 @@ class AIEngineService:
)
# Parse and return the AI response
competitive_intelligence = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
competitive_intelligence = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
competitive_intelligence = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
logger.info("✅ AI competitive intelligence completed")
return competitive_intelligence
@@ -633,8 +667,20 @@ class AIEngineService:
}
)
# Parse and return the AI response
result = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
result = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
result = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
strategic_insights = result.get('strategic_insights', [])
logger.info(f"✅ Generated {len(strategic_insights)} AI strategic insights")
return strategic_insights
@@ -733,8 +779,19 @@ class AIEngineService:
}
)
# Parse and return the AI response
quality_analysis = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
quality_analysis = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
quality_analysis = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
logger.info("✅ AI content quality analysis completed")
return quality_analysis

View File

@@ -12,8 +12,8 @@ import json
from collections import Counter, defaultdict
# Import AI providers
from llm_providers.main_text_generation import llm_text_gen
from llm_providers.gemini_provider import gemini_structured_json_response
from services.llm_providers.main_text_generation import llm_text_gen
from services.llm_providers.gemini_provider import gemini_structured_json_response
# Import existing modules (will be updated to use FastAPI services)
from services.database import get_db_session
@@ -194,8 +194,19 @@ class CompetitorAnalyzer:
}
)
# Parse and return the AI response
market_position = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
market_position = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
market_position = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
logger.info("✅ AI market position analysis completed")
return market_position
@@ -306,8 +317,20 @@ class CompetitorAnalyzer:
}
)
# Parse and return the AI response
result = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
result = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
result = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
content_gaps = result.get('content_gaps', [])
logger.info(f"✅ AI content gap identification completed: {len(content_gaps)} gaps found")
return content_gaps
@@ -399,8 +422,20 @@ class CompetitorAnalyzer:
}
)
# Parse and return the AI response
result = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
result = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
result = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
competitive_insights = result.get('competitive_insights', [])
logger.info(f"✅ AI competitive insights generated: {len(competitive_insights)} insights")
return competitive_insights

View File

@@ -12,8 +12,8 @@ import json
from collections import Counter, defaultdict
# Import AI providers
from llm_providers.main_text_generation import llm_text_gen
from llm_providers.gemini_provider import gemini_structured_json_response
from services.llm_providers.main_text_generation import llm_text_gen
from services.llm_providers.gemini_provider import gemini_structured_json_response
# Import existing modules (will be updated to use FastAPI services)
from services.database import get_db_session
@@ -155,8 +155,19 @@ class KeywordResearcher:
}
)
# Parse and return the AI response
trend_analysis = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
trend_analysis = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
trend_analysis = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
logger.info("✅ AI keyword trend analysis completed")
return trend_analysis
@@ -283,8 +294,20 @@ class KeywordResearcher:
}
)
# Parse and return the AI response
intent_analysis = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
intent_analysis = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
intent_analysis = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
logger.info("✅ AI search intent analysis completed")
return intent_analysis
@@ -396,8 +419,20 @@ class KeywordResearcher:
}
)
# Parse and return the AI response
result = json.loads(response)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
result = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
result = json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI response as JSON: {e}")
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error(f"Unexpected response type from AI service: {type(response)}")
raise Exception(f"Unexpected response type from AI service: {type(response)}")
opportunities = result.get('opportunities', [])
logger.info(f"✅ AI opportunity identification completed: {len(opportunities)} opportunities found")
return opportunities

View File

@@ -14,6 +14,9 @@ from typing import Optional
from models.onboarding import Base as OnboardingBase
from models.seo_analysis import Base as SEOAnalysisBase
from models.content_planning import Base as ContentPlanningBase
from models.enhanced_strategy_models import Base as EnhancedStrategyBase
# Monitoring models now use the same base as enhanced strategy models
from models.monitoring_models import Base as MonitoringBase
# Database configuration
DATABASE_URL = os.getenv('DATABASE_URL', 'sqlite:///./alwrity.db')
@@ -52,6 +55,8 @@ def init_database():
OnboardingBase.metadata.create_all(bind=engine)
SEOAnalysisBase.metadata.create_all(bind=engine)
ContentPlanningBase.metadata.create_all(bind=engine)
EnhancedStrategyBase.metadata.create_all(bind=engine)
MonitoringBase.metadata.create_all(bind=engine)
logger.info("Database initialized successfully with all models")
except SQLAlchemyError as e:
logger.error(f"Error initializing database: {str(e)}")

View File

@@ -0,0 +1,306 @@
# Gemini Provider Module
This module provides functions for interacting with Google's Gemini API, specifically designed for structured JSON output and text generation. It follows the official Gemini API documentation and implements best practices for reliable AI interactions.
## Key Features
- **Structured JSON Response Generation**: Generate structured outputs with schema validation
- **Text Response Generation**: Simple text generation with retry logic
- **Comprehensive Error Handling**: Robust error handling and logging
- **Automatic API Key Management**: Secure API key handling
- **Support for Multiple Models**: gemini-2.5-flash and gemini-2.5-pro
## Best Practices
### 1. Use Structured Output for Complex Responses
```python
# ✅ Good: Use structured output for multi-field responses
schema = {
"type": "object",
"properties": {
"tasks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {"type": "string"},
"description": {"type": "string"}
}
}
}
}
}
result = gemini_structured_json_response(prompt, schema, temperature=0.2, max_tokens=8192)
```
### 2. Keep Schemas Simple and Flat
```python
# ✅ Good: Simple, flat schema
schema = {
"type": "object",
"properties": {
"monitoringTasks": {
"type": "array",
"items": {"type": "object", "properties": {...}}
}
}
}
# ❌ Avoid: Complex nested schemas with many required fields
schema = {
"type": "object",
"required": ["field1", "field2", "field3"],
"properties": {
"field1": {"type": "object", "required": [...], "properties": {...}},
"field2": {"type": "array", "items": {"type": "object", "required": [...], "properties": {...}}}
}
}
```
### 3. Set Appropriate Token Limits
```python
# ✅ Good: Use 8192 tokens for complex outputs
result = gemini_structured_json_response(prompt, schema, max_tokens=8192)
# ✅ Good: Use 2048 tokens for simple text responses
result = gemini_text_response(prompt, max_tokens=2048)
```
### 4. Use Low Temperature for Structured Output
```python
# ✅ Good: Low temperature for consistent structured output
result = gemini_structured_json_response(prompt, schema, temperature=0.1, max_tokens=8192)
# ✅ Good: Higher temperature for creative text
result = gemini_text_response(prompt, temperature=0.8, max_tokens=2048)
```
### 5. Implement Proper Error Handling
```python
# ✅ Good: Handle errors in calling functions
try:
response = gemini_structured_json_response(prompt, schema)
if isinstance(response, dict) and "error" in response:
raise Exception(f"Gemini error: {response.get('error')}")
# Process successful response
except Exception as e:
logger.error(f"AI service error: {e}")
# Handle error appropriately
```
### 6. Avoid Fallback to Text Parsing
```python
# ✅ Good: Use structured output only, no fallback
response = gemini_structured_json_response(prompt, schema)
if "error" in response:
raise Exception(f"Gemini error: {response.get('error')}")
# ❌ Avoid: Fallback to text parsing for structured responses
# This can lead to inconsistent results and parsing errors
```
## Usage Examples
### Structured JSON Response
```python
from services.llm_providers.gemini_provider import gemini_structured_json_response
# Define schema
monitoring_schema = {
"type": "object",
"properties": {
"monitoringTasks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"component": {"type": "string"},
"title": {"type": "string"},
"description": {"type": "string"},
"assignee": {"type": "string"},
"frequency": {"type": "string"},
"metric": {"type": "string"},
"measurementMethod": {"type": "string"},
"successCriteria": {"type": "string"},
"alertThreshold": {"type": "string"},
"actionableInsights": {"type": "string"}
}
}
}
}
}
# Generate structured response
prompt = "Generate a monitoring plan for content strategy..."
result = gemini_structured_json_response(
prompt=prompt,
schema=monitoring_schema,
temperature=0.1,
max_tokens=8192
)
# Handle response
if isinstance(result, dict) and "error" in result:
raise Exception(f"Gemini error: {result.get('error')}")
# Process successful response
monitoring_tasks = result.get("monitoringTasks", [])
```
### Text Response
```python
from services.llm_providers.gemini_provider import gemini_text_response
# Generate text response
prompt = "Write a blog post about AI in content marketing..."
result = gemini_text_response(
prompt=prompt,
temperature=0.8,
max_tokens=2048
)
# Process response
if result:
print(f"Generated text: {result}")
else:
print("No response generated")
```
## Troubleshooting
### Common Issues and Solutions
#### 1. Response.parsed is None
**Symptoms**: `response.parsed` returns `None` even with successful HTTP 200
**Causes**:
- Schema too complex for the model
- Token limit too low
- Temperature too high for structured output
**Solutions**:
- Simplify schema structure
- Increase `max_tokens` to 8192
- Lower temperature to 0.1-0.3
- Test with smaller outputs first
#### 2. JSON Parsing Fails
**Symptoms**: `JSONDecodeError` or "Unterminated string" errors
**Causes**:
- Response truncated due to token limits
- Schema doesn't match expected output
- Model generates malformed JSON
**Solutions**:
- Reduce output size requested
- Verify schema matches expected structure
- Use structured output instead of text parsing
- Increase token limits
#### 3. Truncation Issues
**Symptoms**: Response cuts off mid-sentence or mid-array
**Causes**:
- Output too large for single response
- Token limits exceeded
**Solutions**:
- Reduce number of items requested
- Increase `max_tokens` to 8192
- Break large requests into smaller chunks
- Use `gemini-2.5-pro` for larger outputs
#### 4. Rate Limiting
**Symptoms**: `RetryError` or connection timeouts
**Causes**:
- Too many requests in short time
- Network connectivity issues
**Solutions**:
- Exponential backoff already implemented
- Check network connectivity
- Reduce request frequency
- Verify API key validity
### Debug Logging
The module includes comprehensive debug logging. Enable debug mode to see:
```python
import logging
logging.getLogger('services.llm_providers.gemini_provider').setLevel(logging.DEBUG)
```
Key log messages to monitor:
- `Gemini structured call | prompt_len=X | schema_kind=Y | temp=Z`
- `Gemini response | type=X | has_text=Y | has_parsed=Z`
- `Using response.parsed for structured output`
- `Falling back to response.text parsing`
## API Reference
### gemini_structured_json_response()
Generate structured JSON response using Google's Gemini Pro model.
**Parameters**:
- `prompt` (str): Input prompt for the AI model
- `schema` (dict): JSON schema defining expected output structure
- `temperature` (float): Controls randomness (0.0-1.0). Use 0.1-0.3 for structured output
- `top_p` (float): Nucleus sampling parameter (0.0-1.0)
- `top_k` (int): Top-k sampling parameter
- `max_tokens` (int): Maximum tokens in response. Use 8192 for complex outputs
- `system_prompt` (str, optional): System instruction for the model
**Returns**:
- `dict`: Parsed JSON response matching the provided schema
**Raises**:
- `Exception`: If API key is missing or API call fails
### gemini_text_response()
Generate text response using Google's Gemini Pro model.
**Parameters**:
- `prompt` (str): Input prompt for the AI model
- `temperature` (float): Controls randomness (0.0-1.0). Higher = more creative
- `top_p` (float): Nucleus sampling parameter (0.0-1.0)
- `n` (int): Number of responses to generate
- `max_tokens` (int): Maximum tokens in response
- `system_prompt` (str, optional): System instruction for the model
**Returns**:
- `str`: Generated text response
**Raises**:
- `Exception`: If API key is missing or API call fails
## Dependencies
- `google.generativeai` (genai): Official Gemini API client
- `tenacity`: Retry logic with exponential backoff
- `logging`: Debug and error logging
- `json`: Fallback JSON parsing
- `re`: Text extraction utilities
## Version History
- **v2.0** (January 2025): Enhanced structured output support, improved error handling, comprehensive documentation
- **v1.0**: Initial implementation with basic text and structured response support
## Contributing
When contributing to this module:
1. Follow the established patterns for error handling
2. Add comprehensive logging for debugging
3. Test with both simple and complex schemas
4. Update documentation for any new features
5. Ensure backward compatibility
## Support
For issues or questions:
1. Check the troubleshooting section above
2. Review debug logs for specific error messages
3. Test with simplified schemas to isolate issues
4. Verify API key configuration and network connectivity

View File

@@ -1,4 +1,59 @@
# Using Gemini Pro LLM model
"""
Gemini Provider Module for ALwrity
This module provides functions for interacting with Google's Gemini API, specifically designed
for structured JSON output and text generation. It follows the official Gemini API documentation
and implements best practices for reliable AI interactions.
Key Features:
- Structured JSON response generation with schema validation
- Text response generation with retry logic
- Comprehensive error handling and logging
- Automatic API key management
- Support for both gemini-2.5-flash and gemini-2.5-pro models
Best Practices:
1. Use structured output for complex, multi-field responses
2. Keep schemas simple and flat to avoid truncation
3. Set appropriate token limits (8192 for complex outputs)
4. Use low temperature (0.1-0.3) for consistent structured output
5. Implement proper error handling in calling functions
6. Avoid fallback to text parsing for structured responses
Usage Examples:
# Structured JSON response
schema = {
"type": "object",
"properties": {
"tasks": {
"type": "array",
"items": {"type": "object", "properties": {...}}
}
}
}
result = gemini_structured_json_response(prompt, schema, temperature=0.2, max_tokens=8192)
# Text response
result = gemini_text_response(prompt, temperature=0.7, max_tokens=2048)
Troubleshooting:
- If response.parsed is None: Check schema complexity and token limits
- If JSON parsing fails: Verify schema matches expected output structure
- If truncation occurs: Reduce output size or increase max_tokens
- If rate limiting: Implement exponential backoff (already included)
Dependencies:
- google.generativeai (genai)
- tenacity (for retry logic)
- logging (for debugging)
- json (for fallback parsing)
- re (for text extraction)
Author: ALwrity Team
Version: 2.0
Last Updated: January 2025
"""
import os
import sys
from pathlib import Path
@@ -62,7 +117,39 @@ def get_gemini_api_key() -> str:
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
def gemini_text_response(prompt, temperature, top_p, n, max_tokens, system_prompt):
""" Common functiont to get response from gemini pro Text. """
"""
Generate text response using Google's Gemini Pro model.
This function provides simple text generation with retry logic and error handling.
For structured output, use gemini_structured_json_response instead.
Args:
prompt (str): The input prompt for the AI model
temperature (float): Controls randomness (0.0-1.0). Higher = more creative
top_p (float): Nucleus sampling parameter (0.0-1.0)
n (int): Number of responses to generate
max_tokens (int): Maximum tokens in response
system_prompt (str, optional): System instruction for the model
Returns:
str: Generated text response
Raises:
Exception: If API key is missing or API call fails
Best Practices:
- Use temperature 0.7-0.9 for creative content
- Use temperature 0.1-0.3 for factual/consistent content
- Set appropriate max_tokens based on expected response length
- Implement proper error handling in calling functions
Example:
result = gemini_text_response(
"Write a blog post about AI",
temperature=0.8,
max_tokens=1024
)
"""
#FIXME: Include : https://github.com/google-gemini/cookbook/blob/main/quickstarts/rest/System_instructions_REST.ipynb
try:
api_key = get_gemini_api_key()
@@ -97,51 +184,9 @@ def gemini_text_response(prompt, temperature, top_p, n, max_tokens, system_promp
return response.text
except Exception as err:
logger.error(f"Failed to get response from Gemini: {err}. Retrying.")
raise
#@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
#def gemini_blog_metadata_json(blog_content):
# """ Common functiont to get response from gemini pro Text. """
# prompt = f"I will provide you with the content of a blog post. Based on this content, you need to generate the following elements in JSON format:\n\n1. **Blog Title**: A compelling and relevant title that summarizes the blog content.\n2. **Meta Description**: A concise meta description (up to 160 characters) that captures the essence of the blog post and encourages clicks.\n3. **Tags**: A list of 5-10 relevant tags that represent the key topics covered in the blog post.\n4. **Categories**: A list of 1-3 appropriate categories that best describe the blog post's main themes.\n\nOutput your response in the following JSON format:\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"blog_title\": {\n \"type\": \"string\"\n },\n \"meta_description\": {\n \"type\": \"string\"\n },\n \"tags\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"categories\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n}\n\n. The Blog Content is given below: \n\n{blog_content}\n\n"
#
# try:
# genai.configure(api_key=os.getenv('GEMINI_API_KEY'))
# except Exception as err:
# logger.error(f"Failed to configure Gemini: {err}")
#
# # Create the model
# generation_config = {
# "temperature": 1,
# "top_p": 0.95,
# "top_k": 64,
# "max_output_tokens": 8192,
# "response_schema": content.Schema(
# type = content.Type.OBJECT,
# properties = {
# "response": content.Schema(
# type = content.Type.STRING,
# ),
# },
# ),
# "response_mime_type": "application/json",
# }
#
# model = genai.GenerativeModel(
# model_name="gemini-1.5-flash",
# generation_config=generation_config,
# # safety_settings = Adjust safety settings
# # See https://ai.google.dev/gemini-api/docs/safety-settings
# )
#
# try:
# # text_response = []
# response = model.generate_content(prompt)
# if response:
# logger.info(f"Number of Token in Prompt Sent: {model.count_tokens(prompt)}")
# return response.text
# except Exception as err:
# logger.error(f"Failed to get SEO METADATA from Gemini: {err}. Retrying.")
async def test_gemini_api_key(api_key: str) -> tuple[bool, str]:
"""
Test if the provided Gemini API key is valid.
@@ -243,6 +288,8 @@ def _dict_to_types_schema(schema: Dict[str, Any]) -> types.Schema:
return types.Schema(type=types.Type.ARRAY, items=item_schema)
elif node_type == "NUMBER":
return types.Schema(type=types.Type.NUMBER)
elif node_type == "INTEGER":
return types.Schema(type=types.Type.NUMBER)
elif node_type == "BOOLEAN":
return types.Schema(type=types.Type.BOOLEAN)
else:
@@ -254,6 +301,49 @@ def _dict_to_types_schema(schema: Dict[str, Any]) -> types.Schema:
def gemini_structured_json_response(prompt, schema, temperature=0.7, top_p=0.9, top_k=40, max_tokens=8192, system_prompt=None):
"""
Generate structured JSON response using Google's Gemini Pro model.
This function follows the official Gemini API documentation for structured output:
https://ai.google.dev/gemini-api/docs/structured-output#python
Args:
prompt (str): The input prompt for the AI model
schema (dict): JSON schema defining the expected output structure
temperature (float): Controls randomness (0.0-1.0). Use 0.1-0.3 for structured output
top_p (float): Nucleus sampling parameter (0.0-1.0)
top_k (int): Top-k sampling parameter
max_tokens (int): Maximum tokens in response. Use 8192 for complex outputs
system_prompt (str, optional): System instruction for the model
Returns:
dict: Parsed JSON response matching the provided schema
Raises:
Exception: If API key is missing or API call fails
Best Practices:
- Keep schemas simple and flat to avoid truncation
- Use low temperature (0.1-0.3) for consistent structured output
- Set max_tokens to 8192 for complex multi-field responses
- Avoid deeply nested schemas with many required fields
- Test with smaller outputs first, then scale up
Example:
schema = {
"type": "object",
"properties": {
"tasks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {"type": "string"},
"description": {"type": "string"}
}
}
}
}
}
result = gemini_structured_json_response(prompt, schema, temperature=0.2, max_tokens=8192)
"""
try:
# Get API key with proper error handling
@@ -261,59 +351,65 @@ def gemini_structured_json_response(prompt, schema, temperature=0.7, top_p=0.9,
client = genai.Client(api_key=api_key)
logger.info("✅ Gemini client initialized for structured JSON response")
# Build config using official SDK schema type
# Prepare schema for SDK (dict -> types.Schema). If schema is already a types.Schema or Pydantic type, use as-is
try:
types_schema = _dict_to_types_schema(schema) if isinstance(schema, dict) else schema
if isinstance(schema, dict):
types_schema = _dict_to_types_schema(schema)
else:
types_schema = schema
except Exception as conv_err:
logger.warning(f"Schema conversion warning, defaulting to OBJECT: {conv_err}")
logger.info(f"Schema conversion warning, defaulting to OBJECT: {conv_err}")
types_schema = types.Schema(type=types.Type.OBJECT)
# Add debugging for API call
logger.info(
"Gemini structured call | prompt_len=%s | schema_kind=%s | temp=%s | top_p=%s | top_k=%s | max_tokens=%s",
len(prompt) if isinstance(prompt, str) else '<non-str>',
type(types_schema).__name__,
temperature,
top_p,
top_k,
max_tokens,
)
# Use the official SDK GenerateContentConfig with response_schema
generation_config = types.GenerateContentConfig(
system_instruction=system_prompt,
response_mime_type='application/json',
response_schema=types_schema,
max_output_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
top_k=top_k,
response_mime_type='application/json',
response_schema=types_schema
system_instruction=system_prompt,
)
# Add debugging for API call
logger.debug(f"Gemini API call - prompt length: {len(prompt)}, schema keys: {list(schema.keys()) if isinstance(schema, dict) else 'N/A'}")
response = client.models.generate_content(
model='gemini-2.5-flash',
model="gemini-2.5-flash",
contents=prompt,
config=generation_config,
)
# Add debugging for response
logger.debug(f"Gemini response type: {type(response)}")
logger.debug(f"Gemini response has text: {hasattr(response, 'text')}")
logger.debug(f"Gemini response has parsed: {hasattr(response, 'parsed')}")
logger.info("Gemini response | type=%s | has_text=%s | has_parsed=%s",
type(response), hasattr(response, 'text'), hasattr(response, 'parsed'))
if hasattr(response, 'text'):
logger.debug(f"Gemini response.text: {repr(response.text)}")
logger.info(f"Gemini response.text: {repr(response.text)}")
if hasattr(response, 'parsed'):
logger.debug(f"Gemini response.parsed: {repr(response.parsed)}")
logger.info(f"Gemini response.parsed: {repr(response.parsed)}")
# Prefer parsed if present and non-empty; otherwise parse text with fallbacks
try:
parsed = getattr(response, 'parsed', None)
if parsed:
logger.debug(f"Using parsed response: {type(parsed)}")
return parsed if isinstance(parsed, dict) else json.loads(json.dumps(parsed))
text = (response.text or '').strip()
logger.debug(f"Using text response, length: {len(text)}")
if not text:
logger.error("Gemini returned empty text response")
return {"error": "Empty response from Gemini API", "raw_response": ""}
# According to the documentation, we should use response.parsed for structured output
if hasattr(response, 'parsed') and response.parsed is not None:
logger.info("Using response.parsed for structured output")
return response.parsed
# Fallback to text if parsed is not available
if hasattr(response, 'text') and response.text:
logger.info("Falling back to response.text parsing")
text = response.text.strip()
# Strip markdown code fences if present
if text.startswith('```'):
# remove leading ```json or ``` and trailing ```
if text.lower().startswith('```json'):
text = text[7:]
else:
@@ -322,61 +418,14 @@ def gemini_structured_json_response(prompt, schema, temperature=0.7, top_p=0.9,
text = text[:-3]
text = text.strip()
# Try direct JSON parsing first
try:
return json.loads(text)
except json.JSONDecodeError as e:
logger.warning(f"Direct JSON parsing failed: {e}")
logger.debug(f"Failed to parse text: {text[:200]}...")
# Check if response is truncated (common cause of JSON errors)
if text.endswith('...') or text.endswith('"') or text.endswith(','):
logger.warning("Response appears to be truncated, attempting partial parsing")
# Try to extract what we can from truncated response
partial_result = _extract_partial_json(text)
if partial_result:
logger.info("Successfully extracted partial JSON from truncated response")
return partial_result
# Fallback 1: Extract likely JSON object substring
first = text.find('{')
last = text.rfind('}')
if first != -1 and last != -1 and last > first:
candidate = text[first:last+1]
try:
return json.loads(candidate)
except json.JSONDecodeError:
logger.warning("JSON object extraction failed, trying regex")
# Fallback 2: Regex any object
import re
match = re.search(r'\{[\s\S]*\}', text)
if match:
try:
return json.loads(match.group(0))
except json.JSONDecodeError:
logger.warning("Regex JSON extraction failed, trying repair")
# Fallback 3: Attempt to repair common JSON issues
repaired = _repair_json_string(text)
if repaired:
try:
return json.loads(repaired)
except json.JSONDecodeError:
logger.warning("JSON repair failed")
# Fallback 4: Extract and parse individual key-value pairs
extracted = _extract_key_value_pairs(text)
if extracted:
return extracted
# Final fallback: return error with raw response for debugging
logger.error(f"All JSON parsing attempts failed for text: {text[:200]}...")
logger.error(f"Failed to parse response.text as JSON: {e}")
return {"error": f"Failed to parse JSON response: {e}", "raw_response": text[:500]}
except Exception as e:
logger.error(f"Error parsing structured response: {e}")
return {"error": f"Failed to parse JSON response: {e}", "raw_response": (response.text or '')}
logger.error("No valid response content found")
return {"error": "No valid response content found", "raw_response": ""}
except ValueError as e:
# API key related errors

View File

@@ -0,0 +1,483 @@
import json
import logging
from typing import Dict, Any, List
from datetime import datetime
from services.llm_providers.gemini_provider import gemini_structured_json_response
from services.strategy_service import StrategyService
logger = logging.getLogger(__name__)
class MonitoringPlanGenerator:
def __init__(self):
self.strategy_service = StrategyService()
async def generate_monitoring_plan(self, strategy_id: int) -> Dict[str, Any]:
"""Generate comprehensive monitoring plan for a strategy"""
try:
# Get strategy data
strategy_data = await self.strategy_service.get_strategy_by_id(strategy_id)
if not strategy_data:
raise Exception(f"Strategy with ID {strategy_id} not found")
# Prepare prompt context
prompt_context = self._prepare_prompt_context(strategy_data)
logger.debug(
"MonitoringPlanGenerator: Prepared prompt context | strategy_id=%s | keys=%s",
strategy_id,
list(prompt_context.keys())
)
# Generate monitoring plan using AI
monitoring_plan = await self._generate_plan_with_ai(prompt_context)
# Validate the plan structure
if not self._validate_monitoring_plan(monitoring_plan):
raise Exception("Generated monitoring plan has invalid structure")
# Validate and enhance the plan
enhanced_plan = await self._enhance_monitoring_plan(monitoring_plan, strategy_data)
# Save monitoring plan to database
await self._save_monitoring_plan(strategy_id, enhanced_plan)
logger.info(f"Successfully generated monitoring plan for strategy {strategy_id}")
return enhanced_plan
except Exception as e:
logger.error(f"Error generating monitoring plan for strategy {strategy_id}: {e}")
# Don't mark as success if there's an error
raise Exception(f"Failed to generate monitoring plan: {str(e)}")
def _prepare_prompt_context(self, strategy_data: Dict[str, Any]) -> Dict[str, Any]:
"""Prepare context for AI prompt"""
# Extract strategy components
strategic_insights = strategy_data.get('strategic_insights', {})
competitive_analysis = strategy_data.get('competitive_analysis', {})
performance_predictions = strategy_data.get('performance_predictions', {})
implementation_roadmap = strategy_data.get('implementation_roadmap', {})
risk_assessment = strategy_data.get('risk_assessment', {})
return {
"strategy_name": strategy_data.get('name', 'Content Strategy'),
"industry": strategy_data.get('industry', 'General'),
"business_goals": strategy_data.get('business_goals', []),
"content_pillars": strategy_data.get('content_pillars', []),
"target_audience": strategy_data.get('target_audience', {}),
"competitive_landscape": competitive_analysis.get('competitors', []),
"strategic_insights": strategic_insights,
"performance_predictions": performance_predictions,
"implementation_roadmap": implementation_roadmap,
"risk_assessment": risk_assessment
}
async def _generate_plan_with_ai(self, prompt_context: Dict[str, Any]) -> Dict[str, Any]:
"""Generate monitoring plan using AI"""
prompt = self._build_monitoring_prompt(prompt_context)
logger.debug(
"MonitoringPlanGenerator: Built prompt | length=%s | preview=%s...",
len(prompt),
(prompt[:240].replace("\n", " ") if isinstance(prompt, str) else "<non-str>")
)
# Define schema for 8 tasks (2 per component) to avoid truncation
monitoring_plan_schema = {
"type": "object",
"properties": {
"monitoringTasks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"component": {"type": "string"},
"title": {"type": "string"},
"description": {"type": "string"},
"assignee": {"type": "string"},
"frequency": {"type": "string"},
"metric": {"type": "string"},
"measurementMethod": {"type": "string"},
"successCriteria": {"type": "string"},
"alertThreshold": {"type": "string"},
"actionableInsights": {"type": "string"}
}
}
}
}
}
logger.debug(
"MonitoringPlanGenerator: Schema prepared | schema_type=%s",
type(monitoring_plan_schema).__name__
)
try:
# Structured response only (no fallback)
logger.info("MonitoringPlanGenerator: Invoking Gemini structured JSON response")
response = gemini_structured_json_response(
prompt=prompt,
schema=monitoring_plan_schema,
temperature=0.1,
max_tokens=8192
)
logger.debug(
"MonitoringPlanGenerator: Received AI response | type=%s",
type(response)
)
# Handle response - gemini_structured_json_response returns dict directly
if isinstance(response, dict):
if "error" in response:
logger.error("MonitoringPlanGenerator: Gemini returned error dict | error=%s", response.get("error"))
raise Exception(f"Gemini error: {response.get('error')}")
logger.debug(
"MonitoringPlanGenerator: Parsed response dict keys=%s",
list(response.keys())
)
monitoring_plan = response
elif isinstance(response, str):
# If it's a string, try to parse as JSON
try:
monitoring_plan = json.loads(response)
except json.JSONDecodeError as e:
logger.error("MonitoringPlanGenerator: Failed to parse AI response as JSON: %s", e)
raise Exception(f"Invalid AI response format: {str(e)}")
else:
logger.error("MonitoringPlanGenerator: Unexpected response type from AI service: %s", type(response))
raise Exception(f"Unexpected response type from AI service: {type(response)}")
logger.info(
"MonitoringPlanGenerator: AI monitoring plan generated | has_tasks=%s",
isinstance(monitoring_plan.get("monitoringTasks"), list)
)
# Compute totals from the returned tasks
monitoring_tasks = monitoring_plan.get("monitoringTasks", [])
total_tasks = len(monitoring_tasks)
alwrity_tasks = sum(1 for task in monitoring_tasks if task.get("assignee") == "ALwrity")
human_tasks = sum(1 for task in monitoring_tasks if task.get("assignee") == "Human")
# Add computed totals to the plan
monitoring_plan["totalTasks"] = total_tasks
monitoring_plan["alwrityTasks"] = alwrity_tasks
monitoring_plan["humanTasks"] = human_tasks
monitoring_plan["metricsCount"] = total_tasks
logger.info(
"MonitoringPlanGenerator: Computed totals | total=%s | alwrity=%s | human=%s",
total_tasks, alwrity_tasks, human_tasks
)
return monitoring_plan
except Exception as e:
logger.error(f"Error calling AI service: {e}")
raise Exception(f"AI service error: {str(e)}")
def _build_monitoring_prompt(self, context: Dict[str, Any]) -> str:
"""Build the AI prompt for monitoring plan generation"""
return f"""Generate a monitoring plan for content strategy: {context['strategy_name']} in {context['industry']} industry.
Create exactly 8 monitoring tasks (2 per component) across 5 strategy components:
1. Strategic Insights
2. Competitive Analysis
3. Performance Predictions
4. Implementation Roadmap
5. Risk Assessment
Each task must include: component, title, description, assignee (ALwrity or Human), frequency (Daily, Weekly, Monthly, or Quarterly), metric, measurement method, success criteria, alert threshold, and actionable insights.
Return a JSON object with monitoringTasks array containing 8 task objects."""
def _generate_default_plan(self, context: Dict[str, Any]) -> Dict[str, Any]:
"""Generate a default monitoring plan if AI fails"""
return {
"totalTasks": 15,
"alwrityTasks": 10,
"humanTasks": 5,
"metricsCount": 15,
"components": [
{
"name": "Strategic Insights",
"icon": "TrendingUpIcon",
"tasks": [
{
"title": "Monitor Market Positioning Effectiveness",
"description": "Track how well the strategic positioning is performing in the market",
"assignee": "ALwrity",
"frequency": "Weekly",
"metric": "Market Position Score",
"measurementMethod": "Competitive analysis and brand mention tracking",
"successCriteria": "Maintain top 3 market position",
"alertThreshold": "Drop below top 5 position"
},
{
"title": "Track Strategic Goal Achievement",
"description": "Monitor progress toward defined business objectives",
"assignee": "Human",
"frequency": "Monthly",
"metric": "Goal Achievement Rate",
"measurementMethod": "KPI tracking and business metrics analysis",
"successCriteria": "Achieve 80% of strategic goals",
"alertThreshold": "Drop below 60% achievement"
},
{
"title": "Analyze Strategic Insights Performance",
"description": "Evaluate the effectiveness of strategic insights and recommendations",
"assignee": "ALwrity",
"frequency": "Weekly",
"metric": "Insight Effectiveness Score",
"measurementMethod": "Performance data analysis and trend identification",
"successCriteria": "Maintain 85%+ effectiveness score",
"alertThreshold": "Drop below 70% effectiveness"
}
]
},
{
"name": "Competitive Analysis",
"icon": "EmojiEventsIcon",
"tasks": [
{
"title": "Monitor Competitor Activities",
"description": "Track competitor content strategies and market activities",
"assignee": "ALwrity",
"frequency": "Daily",
"metric": "Competitor Activity Score",
"measurementMethod": "Automated competitor monitoring and analysis",
"successCriteria": "Stay ahead of competitor activities",
"alertThreshold": "Competitor gains significant advantage"
},
{
"title": "Track Competitive Positioning",
"description": "Monitor our competitive position in the market",
"assignee": "ALwrity",
"frequency": "Weekly",
"metric": "Competitive Position Rank",
"measurementMethod": "Market share and positioning analysis",
"successCriteria": "Maintain top 3 competitive position",
"alertThreshold": "Drop below top 5 position"
},
{
"title": "Validate Competitive Intelligence",
"description": "Review and validate competitive analysis insights",
"assignee": "Human",
"frequency": "Monthly",
"metric": "Intelligence Accuracy Score",
"measurementMethod": "Manual review and validation",
"successCriteria": "Maintain 90%+ accuracy",
"alertThreshold": "Drop below 80% accuracy"
}
]
},
{
"name": "Performance Predictions",
"icon": "AssessmentIcon",
"tasks": [
{
"title": "Monitor Prediction Accuracy",
"description": "Track the accuracy of performance predictions",
"assignee": "ALwrity",
"frequency": "Weekly",
"metric": "Prediction Accuracy Rate",
"measurementMethod": "Compare predictions with actual performance",
"successCriteria": "Maintain 85%+ prediction accuracy",
"alertThreshold": "Drop below 70% accuracy"
},
{
"title": "Update Prediction Models",
"description": "Refine prediction models based on new data",
"assignee": "ALwrity",
"frequency": "Monthly",
"metric": "Model Performance Score",
"measurementMethod": "Model validation and performance testing",
"successCriteria": "Improve model performance by 5%+",
"alertThreshold": "Model performance degrades"
},
{
"title": "Review Prediction Insights",
"description": "Analyze prediction insights and business implications",
"assignee": "Human",
"frequency": "Monthly",
"metric": "Insight Actionability Score",
"measurementMethod": "Manual review and business analysis",
"successCriteria": "Generate actionable insights",
"alertThreshold": "Insights become less actionable"
}
]
},
{
"name": "Implementation Roadmap",
"icon": "CheckCircleIcon",
"tasks": [
{
"title": "Track Implementation Progress",
"description": "Monitor progress on implementation roadmap milestones",
"assignee": "ALwrity",
"frequency": "Weekly",
"metric": "Implementation Progress Rate",
"measurementMethod": "Milestone tracking and progress analysis",
"successCriteria": "Achieve 90%+ of milestones on time",
"alertThreshold": "Fall behind by more than 2 weeks"
},
{
"title": "Monitor Resource Utilization",
"description": "Track resource allocation and utilization efficiency",
"assignee": "ALwrity",
"frequency": "Weekly",
"metric": "Resource Efficiency Score",
"measurementMethod": "Resource tracking and efficiency analysis",
"successCriteria": "Maintain 85%+ resource efficiency",
"alertThreshold": "Drop below 70% efficiency"
},
{
"title": "Review Implementation Effectiveness",
"description": "Evaluate the effectiveness of implementation strategies",
"assignee": "Human",
"frequency": "Monthly",
"metric": "Implementation Success Rate",
"measurementMethod": "Manual review and effectiveness assessment",
"successCriteria": "Achieve 80%+ implementation success",
"alertThreshold": "Drop below 60% success rate"
}
]
},
{
"name": "Risk Assessment",
"icon": "StarIcon",
"tasks": [
{
"title": "Monitor Risk Indicators",
"description": "Track identified risk factors and their status",
"assignee": "ALwrity",
"frequency": "Daily",
"metric": "Risk Level Score",
"measurementMethod": "Risk factor monitoring and analysis",
"successCriteria": "Maintain low risk level (score < 30)",
"alertThreshold": "Risk level increases above 50"
},
{
"title": "Track Risk Mitigation Effectiveness",
"description": "Monitor the effectiveness of risk mitigation strategies",
"assignee": "ALwrity",
"frequency": "Weekly",
"metric": "Mitigation Effectiveness Rate",
"measurementMethod": "Risk reduction tracking and analysis",
"successCriteria": "Achieve 80%+ risk mitigation success",
"alertThreshold": "Drop below 60% mitigation success"
},
{
"title": "Review Risk Management Decisions",
"description": "Evaluate risk management decisions and their outcomes",
"assignee": "Human",
"frequency": "Monthly",
"metric": "Risk Management Score",
"measurementMethod": "Manual review and decision analysis",
"successCriteria": "Maintain 85%+ risk management effectiveness",
"alertThreshold": "Drop below 70% effectiveness"
}
]
}
]
}
async def _enhance_monitoring_plan(self, plan: Dict[str, Any], strategy_data: Dict[str, Any]) -> Dict[str, Any]:
"""Enhance AI-generated plan with additional context and validation"""
enhanced_plan = plan.copy()
# Add monitoring schedule
enhanced_plan["monitoringSchedule"] = {
"dailyChecks": ["Performance metrics", "Alert monitoring", "Risk indicators"],
"weeklyReviews": ["Trend analysis", "Competitive updates", "Implementation progress"],
"monthlyAssessments": ["Strategy effectiveness", "Goal progress", "Risk management"],
"quarterlyPlanning": ["Strategy optimization", "Goal refinement", "Resource allocation"]
}
# Add success metrics
enhanced_plan["successMetrics"] = {
"trafficGrowth": {"target": "25%+", "current": "0%"},
"engagementRate": {"target": "15%+", "current": "0%"},
"conversionRate": {"target": "10%+", "current": "0%"},
"roi": {"target": "3:1+", "current": "0:1"},
"strategyAdoption": {"target": "90%+", "current": "0%"},
"contentQuality": {"target": "85%+", "current": "0%"},
"competitivePosition": {"target": "Top 3", "current": "Unknown"},
"audienceGrowth": {"target": "20%+", "current": "0%"}
}
# Add metadata
enhanced_plan["metadata"] = {
"generatedAt": datetime.now().isoformat(),
"strategyId": strategy_data.get('id'),
"strategyName": strategy_data.get('name'),
"version": "1.0"
}
return enhanced_plan
async def _save_monitoring_plan(self, strategy_id: int, plan: Dict[str, Any]):
"""Save monitoring plan to database"""
try:
# Use the strategy service to save the monitoring plan
success = await self.strategy_service.save_monitoring_plan(strategy_id, plan)
if success:
logger.info(f"Monitoring plan saved to database for strategy {strategy_id}")
else:
logger.warning(f"Failed to save monitoring plan to database for strategy {strategy_id}")
except Exception as e:
logger.error(f"Error saving monitoring plan: {e}")
# Don't raise the error as the plan generation was successful
def _validate_monitoring_plan(self, plan: Dict[str, Any]) -> bool:
"""Validate the structure of the generated monitoring plan"""
try:
# Check that monitoringTasks is a list and has content
monitoring_tasks = plan.get("monitoringTasks", [])
if not isinstance(monitoring_tasks, list):
logger.error("monitoringTasks must be a list")
return False
if len(monitoring_tasks) == 0:
logger.error("No monitoring tasks generated")
return False
# Validate we have the expected number of tasks (8)
if len(monitoring_tasks) != 8:
logger.warning(f"Expected 8 tasks, got {len(monitoring_tasks)}")
# Validate each task structure
required_task_fields = [
"component", "title", "description", "assignee", "frequency",
"metric", "measurementMethod", "successCriteria", "alertThreshold", "actionableInsights"
]
for i, task in enumerate(monitoring_tasks):
for field in required_task_fields:
if field not in task:
logger.error(f"Task {i} missing required field: {field}")
return False
# Validate assignee is either "ALwrity" or "Human"
if task.get("assignee") not in ["ALwrity", "Human"]:
logger.error(f"Task {i} has invalid assignee: {task.get('assignee')}")
return False
# Validate computed totals are present (added after AI response)
computed_fields = ["totalTasks", "alwrityTasks", "humanTasks", "metricsCount"]
for field in computed_fields:
if field not in plan:
logger.error(f"Missing computed field in monitoring plan: {field}")
return False
return True
except Exception as e:
logger.error(f"Error validating monitoring plan: {e}")
return False

View File

@@ -0,0 +1,383 @@
import logging
from typing import Dict, Any, Optional, List
from datetime import datetime
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_
from models.monitoring_models import (
StrategyMonitoringPlan,
MonitoringTask,
TaskExecutionLog,
StrategyPerformanceMetrics,
StrategyActivationStatus
)
from models.enhanced_strategy_models import EnhancedContentStrategy
from services.database import get_db_session
logger = logging.getLogger(__name__)
class StrategyService:
"""Service for managing content strategies and their activation status"""
def __init__(self, db_session: Optional[Session] = None):
self.db_session = db_session or get_db_session()
async def get_strategy_by_id(self, strategy_id: int) -> Optional[Dict[str, Any]]:
"""Get strategy by ID with all related data"""
try:
if self.db_session:
# Query the actual database
strategy = self.db_session.query(EnhancedContentStrategy).filter(
EnhancedContentStrategy.id == strategy_id
).first()
if strategy:
return strategy.to_dict()
# Fallback to mock data if no database or strategy not found
strategy_data = {
'id': strategy_id,
'name': f'Content Strategy {strategy_id}',
'industry': 'Technology',
'business_goals': ['Increase brand awareness', 'Generate leads', 'Improve engagement'],
'content_pillars': ['Educational Content', 'Thought Leadership', 'Case Studies'],
'target_audience': {
'demographics': 'B2B professionals',
'age_range': '25-45',
'interests': ['technology', 'business', 'innovation']
},
'strategic_insights': {
'market_positioning': 'Innovation leader in tech solutions',
'content_opportunities': ['AI trends', 'Digital transformation', 'Industry insights'],
'growth_potential': 'High growth potential in emerging markets'
},
'competitive_analysis': {
'competitors': ['Competitor A', 'Competitor B', 'Competitor C'],
'market_gaps': ['AI implementation guidance', 'ROI measurement tools'],
'opportunities': ['Thought leadership in AI', 'Educational content series']
},
'performance_predictions': {
'estimated_roi': '25-35%',
'traffic_growth': '40% increase in 6 months',
'engagement_metrics': '15% improvement in engagement rate'
},
'implementation_roadmap': {
'phases': ['Foundation', 'Growth', 'Optimization', 'Scale'],
'timeline': '12 months',
'milestones': ['Month 3: Content foundation', 'Month 6: Growth phase', 'Month 9: Optimization']
},
'risk_assessment': {
'risks': ['Market competition', 'Resource constraints', 'Technology changes'],
'overall_risk_level': 'Medium',
'mitigation_strategies': ['Continuous monitoring', 'Agile adaptation', 'Resource planning']
}
}
logger.info(f"Retrieved strategy {strategy_id}")
return strategy_data
except Exception as e:
logger.error(f"Error retrieving strategy {strategy_id}: {e}")
return None
async def activate_strategy(self, strategy_id: int, user_id: int = 1) -> bool:
"""Activate a strategy and set up monitoring"""
try:
# Check if strategy exists
strategy = await self.get_strategy_by_id(strategy_id)
if not strategy:
logger.error(f"Strategy {strategy_id} not found")
return False
# Check if already activated
if self.db_session:
existing_activation = self.db_session.query(StrategyActivationStatus).filter(
and_(
StrategyActivationStatus.strategy_id == strategy_id,
StrategyActivationStatus.user_id == user_id,
StrategyActivationStatus.status == 'active'
)
).first()
if existing_activation:
logger.info(f"Strategy {strategy_id} is already active")
return True
# Create activation status record
activation_status = StrategyActivationStatus(
strategy_id=strategy_id,
user_id=user_id,
activation_date=datetime.utcnow(),
status='active',
performance_score=0.0
)
if self.db_session:
self.db_session.add(activation_status)
self.db_session.commit()
logger.info(f"Strategy {strategy_id} activated successfully")
else:
logger.info(f"Strategy {strategy_id} activated (no database session)")
return True
except Exception as e:
logger.error(f"Error activating strategy {strategy_id}: {e}")
if self.db_session:
self.db_session.rollback()
return False
async def save_monitoring_plan(self, strategy_id: int, plan_data: Dict[str, Any]) -> bool:
"""Save monitoring plan to database"""
try:
# Check if monitoring plan already exists
if self.db_session:
existing_plan = self.db_session.query(StrategyMonitoringPlan).filter(
StrategyMonitoringPlan.strategy_id == strategy_id
).first()
if existing_plan:
# Update existing plan
existing_plan.plan_data = plan_data
existing_plan.updated_at = datetime.utcnow()
else:
# Create new monitoring plan
monitoring_plan = StrategyMonitoringPlan(
strategy_id=strategy_id,
plan_data=plan_data
)
self.db_session.add(monitoring_plan)
# Clear existing tasks and create new ones
self.db_session.query(MonitoringTask).filter(
MonitoringTask.strategy_id == strategy_id
).delete()
# Create individual monitoring tasks
for component in plan_data.get('components', []):
for task in component.get('tasks', []):
monitoring_task = MonitoringTask(
strategy_id=strategy_id,
component_name=component['name'],
task_title=task['title'],
task_description=task['description'],
assignee=task['assignee'],
frequency=task['frequency'],
metric=task['metric'],
measurement_method=task['measurementMethod'],
success_criteria=task['successCriteria'],
alert_threshold=task['alertThreshold'],
status='pending'
)
self.db_session.add(monitoring_task)
self.db_session.commit()
logger.info(f"Monitoring plan saved for strategy {strategy_id}")
else:
logger.info(f"Monitoring plan prepared for strategy {strategy_id} (no database session)")
return True
except Exception as e:
logger.error(f"Error saving monitoring plan for strategy {strategy_id}: {e}")
if self.db_session:
self.db_session.rollback()
return False
async def get_monitoring_plan(self, strategy_id: int) -> Optional[Dict[str, Any]]:
"""Get monitoring plan for a strategy"""
try:
if self.db_session:
monitoring_plan = self.db_session.query(StrategyMonitoringPlan).filter(
StrategyMonitoringPlan.strategy_id == strategy_id
).first()
if monitoring_plan:
return monitoring_plan.plan_data
# Also check activation status
activation_status = self.db_session.query(StrategyActivationStatus).filter(
StrategyActivationStatus.strategy_id == strategy_id
).first()
if activation_status:
return {
'strategy_id': strategy_id,
'status': activation_status.status,
'activation_date': activation_status.activation_date.isoformat(),
'message': 'Strategy is active but no monitoring plan found'
}
# Fallback to mock data
return {
'strategy_id': strategy_id,
'status': 'active',
'message': 'Monitoring plan retrieved successfully'
}
except Exception as e:
logger.error(f"Error getting monitoring plan for strategy {strategy_id}: {e}")
return None
async def update_strategy_status(self, strategy_id: int, status: str, user_id: int = 1) -> bool:
"""Update strategy activation status"""
try:
if self.db_session:
activation_status = self.db_session.query(StrategyActivationStatus).filter(
and_(
StrategyActivationStatus.strategy_id == strategy_id,
StrategyActivationStatus.user_id == user_id
)
).first()
if activation_status:
activation_status.status = status
activation_status.last_updated = datetime.utcnow()
self.db_session.commit()
logger.info(f"Strategy {strategy_id} status updated to {status}")
return True
else:
logger.warning(f"No activation status found for strategy {strategy_id}")
return False
else:
logger.info(f"Strategy {strategy_id} status would be updated to {status} (no database session)")
return True
except Exception as e:
logger.error(f"Error updating strategy status for {strategy_id}: {e}")
if self.db_session:
self.db_session.rollback()
return False
async def get_active_strategies(self, user_id: int = 1) -> List[Dict[str, Any]]:
"""Get all active strategies for a user"""
try:
if self.db_session:
active_strategies = self.db_session.query(StrategyActivationStatus).filter(
and_(
StrategyActivationStatus.user_id == user_id,
StrategyActivationStatus.status == 'active'
)
).all()
return [
{
'strategy_id': strategy.strategy_id,
'activation_date': strategy.activation_date,
'performance_score': strategy.performance_score,
'last_updated': strategy.last_updated
}
for strategy in active_strategies
]
else:
# Return mock data
return [
{
'strategy_id': 1,
'activation_date': datetime.utcnow(),
'performance_score': 0.0,
'last_updated': datetime.utcnow()
}
]
except Exception as e:
logger.error(f"Error getting active strategies for user {user_id}: {e}")
return []
async def save_performance_metrics(self, strategy_id: int, metrics: Dict[str, Any], user_id: int = 1) -> bool:
"""Save performance metrics for a strategy"""
try:
performance_metrics = StrategyPerformanceMetrics(
strategy_id=strategy_id,
user_id=user_id,
metric_date=datetime.utcnow(),
traffic_growth_percentage=metrics.get('traffic_growth_percentage'),
engagement_rate_percentage=metrics.get('engagement_rate_percentage'),
conversion_rate_percentage=metrics.get('conversion_rate_percentage'),
roi_ratio=metrics.get('roi_ratio'),
strategy_adoption_rate=metrics.get('strategy_adoption_rate'),
content_quality_score=metrics.get('content_quality_score'),
competitive_position_rank=metrics.get('competitive_position_rank'),
audience_growth_percentage=metrics.get('audience_growth_percentage'),
data_source=metrics.get('data_source', 'manual'),
confidence_score=metrics.get('confidence_score', 0.8)
)
if self.db_session:
self.db_session.add(performance_metrics)
self.db_session.commit()
logger.info(f"Performance metrics saved for strategy {strategy_id}")
else:
logger.info(f"Performance metrics prepared for strategy {strategy_id} (no database session)")
return True
except Exception as e:
logger.error(f"Error saving performance metrics for strategy {strategy_id}: {e}")
if self.db_session:
self.db_session.rollback()
return False
async def get_strategy_performance_history(self, strategy_id: int, days: int = 30) -> List[Dict[str, Any]]:
"""Get performance history for a strategy"""
try:
if self.db_session:
from datetime import timedelta
cutoff_date = datetime.utcnow() - timedelta(days=days)
metrics = self.db_session.query(StrategyPerformanceMetrics).filter(
and_(
StrategyPerformanceMetrics.strategy_id == strategy_id,
StrategyPerformanceMetrics.metric_date >= cutoff_date
)
).order_by(StrategyPerformanceMetrics.metric_date.desc()).all()
return [
{
'date': metric.metric_date.isoformat(),
'traffic_growth': metric.traffic_growth_percentage,
'engagement_rate': metric.engagement_rate_percentage,
'conversion_rate': metric.conversion_rate_percentage,
'roi': metric.roi_ratio,
'strategy_adoption': metric.strategy_adoption_rate,
'content_quality': metric.content_quality_score,
'competitive_position': metric.competitive_position_rank,
'audience_growth': metric.audience_growth_percentage
}
for metric in metrics
]
else:
return []
except Exception as e:
logger.error(f"Error getting performance history for strategy {strategy_id}: {e}")
return []
async def deactivate_strategy(self, strategy_id: int, user_id: int = 1) -> bool:
"""Deactivate a strategy"""
try:
return await self.update_strategy_status(strategy_id, 'inactive', user_id)
except Exception as e:
logger.error(f"Error deactivating strategy {strategy_id}: {e}")
return False
async def pause_strategy(self, strategy_id: int, user_id: int = 1) -> bool:
"""Pause a strategy"""
try:
return await self.update_strategy_status(strategy_id, 'paused', user_id)
except Exception as e:
logger.error(f"Error pausing strategy {strategy_id}: {e}")
return False
async def resume_strategy(self, strategy_id: int, user_id: int = 1) -> bool:
"""Resume a paused strategy"""
try:
return await self.update_strategy_status(strategy_id, 'active', user_id)
except Exception as e:
logger.error(f"Error resuming strategy {strategy_id}: {e}")
return False
def __del__(self):
"""Cleanup database session"""
if self.db_session:
self.db_session.close()