Content Calendar, Content Gap Analysis, and Content Optimization

This commit is contained in:
ajaysi
2025-05-27 09:15:08 +05:30
parent 4049d19787
commit 889021c078
100 changed files with 18504 additions and 1251 deletions

View File

@@ -0,0 +1,167 @@
# Content Calendar & Topic Planning System
A comprehensive content planning and scheduling system that leverages existing SEO tools and AI capabilities to create optimized content calendars based on content gap analysis.
## Folder Structure
```
content_calendar/
├── README.md
├── core/
│ ├── __init__.py
│ ├── calendar_manager.py # Main calendar management system
│ ├── topic_generator.py # AI-powered topic generation
│ └── content_predictor.py # Content performance prediction
├── integrations/
│ ├── __init__.py
│ ├── seo_tools.py # Integration with existing SEO tools
│ ├── gap_analyzer.py # Content gap analysis integration
│ └── platform_adapters.py # Platform-specific content adaptation
├── models/
│ ├── __init__.py
│ ├── calendar.py # Calendar data models
│ ├── content.py # Content data models
│ └── analytics.py # Analytics data models
├── utils/
│ ├── __init__.py
│ ├── date_utils.py # Date and scheduling utilities
│ ├── validation.py # Input validation
│ └── error_handling.py # Error handling utilities
└── tests/
├── __init__.py
├── test_calendar.py
├── test_topic_generator.py
└── test_integrations.py
```
## Implementation Plan
### Phase 1: Core Infrastructure
1. **Basic Calendar Management**
- Implement calendar data structures
- Create scheduling algorithms
- Build date management utilities
2. **Topic Generation System**
- Integrate with existing AI tools
- Implement topic generation logic
- Add SEO optimization features
3. **Integration Framework**
- Connect with existing SEO tools
- Implement content gap analysis integration
- Create platform-specific adapters
### Phase 2: AI & SEO Enhancement
1. **AI-Powered Features**
- Implement topic ideation
- Add content structure generation
- Create performance prediction models
2. **SEO Optimization**
- Integrate title optimization
- Add meta description generation
- Implement structured data creation
3. **Content Performance**
- Add performance tracking
- Implement analytics collection
- Create reporting system
### Phase 3: UI Development
1. **Calendar Interface**
- Create interactive calendar view
- Implement drag-and-drop functionality
- Add platform-specific views
2. **Content Planning Panel**
- Build topic suggestion interface
- Create SEO metrics display
- Implement content gap visualization
3. **Analytics Dashboard**
- Design performance metrics view
- Create engagement tracking
- Implement progress monitoring
### Phase 4: Testing & Refinement
1. **Testing**
- Unit testing
- Integration testing
- User acceptance testing
2. **Optimization**
- Performance optimization
- Code refactoring
- Bug fixes
3. **Documentation**
- API documentation
- User guides
- Integration guides
## Integration with Existing Tools
### SEO Tools Integration
- `content_title_generator.py` - For optimized titles
- `meta_desc_generator.py` - For meta descriptions
- `seo_structured_data.py` - For structured data
- `content_gap_analysis/` - For gap analysis
- `webpage_content_analysis.py` - For content analysis
### AI Capabilities
- Leverage existing `llm_text_gen` for:
- Topic generation
- Content structure
- Performance prediction
## Key Features
1. **Content Planning**
- AI-powered topic generation
- SEO-optimized content scheduling
- Platform-specific planning
2. **SEO Integration**
- Automated SEO optimization
- Performance tracking
- Gap analysis integration
3. **Analytics & Reporting**
- Content performance metrics
- SEO impact tracking
- Platform engagement stats
## Getting Started
1. **Prerequisites**
- Python 3.8+
- Access to existing SEO tools
- Required API keys
2. **Installation**
```bash
# Add installation steps here
```
3. **Configuration**
```python
# Add configuration example here
```
4. **Basic Usage**
```python
# Add usage example here
```
## Contributing
Guidelines for contributing to the project.
## License
Project license information.

View File

@@ -0,0 +1,798 @@
from typing import Dict, List, Any, Optional
import logging
from pathlib import Path
import sys
import json
# Add parent directory to path to import existing tools
parent_dir = str(Path(__file__).parent.parent.parent.parent)
if parent_dir not in sys.path:
sys.path.append(parent_dir)
from lib.ai_seo_tools.content_calendar.models.calendar import ContentType, ContentItem, Platform
from lib.ai_seo_tools.content_calendar.utils.error_handling import handle_calendar_error
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from lib.ai_seo_tools.content_gap_analysis.main import ContentGapAnalysis
from lib.ai_seo_tools.content_title_generator import ai_title_generator
from lib.ai_seo_tools.meta_desc_generator import metadesc_generator_main
logger = logging.getLogger(__name__)
class AIGenerator:
"""AI-powered content generation and enhancement."""
def __init__(self):
self.logger = logging.getLogger('content_calendar.ai_generator')
self.logger.info("Initializing AIGenerator")
self._setup_logging()
self._load_ai_tools()
def _setup_logging(self):
"""Configure logging for AI generator."""
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
def _load_ai_tools(self):
"""Load and initialize AI tools."""
try:
# Initialize AI tools
self.gap_analyzer = ContentGapAnalysis()
self.title_generator = ai_title_generator
self.meta_generator = metadesc_generator_main
except Exception as e:
logger.error(f"Error loading AI tools: {str(e)}")
raise
def generate_content(self, content_item: ContentItem, target_audience: Dict[str, Any]) -> Dict[str, Any]:
"""Generate base content using AI."""
try:
self.logger.info(f"Generating content for: {content_item.title}")
# Generate content based on type and platform
content = {
'title': content_item.title,
'content_flow': {
'introduction': {
'summary': f"An engaging introduction about {content_item.title}",
'key_points': [
f"Key point 1 about {content_item.title}",
f"Key point 2 about {content_item.title}",
f"Key point 3 about {content_item.title}"
]
},
'main_content': {
'sections': [
{
'title': f"Section 1: Understanding {content_item.title}",
'content': f"Detailed content about {content_item.title}",
'subsections': []
},
{
'title': f"Section 2: Best Practices for {content_item.title}",
'content': "Best practices and recommendations",
'subsections': []
}
]
},
'conclusion': {
'summary': f"Concluding thoughts about {content_item.title}",
'call_to_action': "Next steps and actions"
}
},
'metadata': {
'tone': target_audience.get('content_settings', {}).get('tone', 'professional'),
'length': target_audience.get('content_settings', {}).get('length', 'medium'),
'platform': content_item.platforms[0].name if content_item.platforms else 'Unknown',
'content_type': content_item.content_type.name
}
}
return content
except Exception as e:
self.logger.error(f"Error generating content: {str(e)}", exc_info=True)
return {}
def enhance_content(self, content: ContentItem, enhancement_type: str, target_audience: Dict[str, Any]) -> Dict[str, Any]:
"""Enhance existing content using AI."""
try:
self.logger.info(f"Enhancing content: {content.title}")
# Enhance content based on type
enhanced = {
'content': f"Enhanced version of {content.description}",
'changes': [
"Improved readability",
"Enhanced engagement elements",
"Optimized for target audience"
],
'metadata': {
'enhancement_type': enhancement_type,
'target_audience': target_audience
}
}
return enhanced
except Exception as e:
self.logger.error(f"Error enhancing content: {str(e)}", exc_info=True)
return {}
def enhance_for_platform(self, content: Dict[str, Any], platform: Platform, enhancement_type: str) -> Dict[str, Any]:
"""Enhance content specifically for a platform."""
try:
self.logger.info(f"Enhancing content for platform: {platform.name}")
# Platform-specific enhancements
enhanced = {
'content': content.get('content', ''),
'changes': [
f"Optimized for {platform.name}",
"Platform-specific formatting",
"Enhanced engagement elements"
],
'metadata': {
'platform': platform.name,
'enhancement_type': enhancement_type
}
}
return enhanced
except Exception as e:
self.logger.error(f"Error enhancing for platform: {str(e)}", exc_info=True)
return {}
def enhance_variant(self, content: Dict[str, Any], variant_type: str, optimization_goals: List[str]) -> Dict[str, Any]:
"""Enhance a content variant for A/B testing."""
try:
self.logger.info(f"Enhancing variant: {variant_type}")
# Variant-specific enhancements
enhanced = {
'content': content.get('content', ''),
'changes': [
f"Optimized for {', '.join(optimization_goals)}",
"Enhanced variant-specific elements",
"Improved engagement metrics"
],
'metadata': {
'variant_type': variant_type,
'optimization_goals': optimization_goals
}
}
return enhanced
except Exception as e:
self.logger.error(f"Error enhancing variant: {str(e)}", exc_info=True)
return {}
def enhance_for_seo(self, content: Dict[str, Any], seo_goals: List[str]) -> Dict[str, Any]:
"""Enhance content for SEO optimization."""
try:
self.logger.info("Enhancing content for SEO")
# SEO-specific enhancements
enhanced = {
'content': content.get('content', ''),
'changes': [
f"Optimized for {', '.join(seo_goals)}",
"Enhanced keyword placement",
"Improved meta information"
],
'metadata': {
'seo_goals': seo_goals
}
}
return enhanced
except Exception as e:
self.logger.error(f"Error enhancing for SEO: {str(e)}", exc_info=True)
return {}
def generate_series_content(self, content_item: ContentItem, series_info: Dict[str, Any]) -> Dict[str, Any]:
"""Generate content for a series."""
try:
self.logger.info(f"Generating series content: {content_item.title}")
# Generate series-specific content
content = {
'title': content_item.title,
'content_flow': {
'introduction': {
'summary': f"Part {series_info['part_number']} of {series_info['total_parts']} about {series_info['topic']}",
'key_points': [
f"Key point 1 for part {series_info['part_number']}",
f"Key point 2 for part {series_info['part_number']}",
f"Key point 3 for part {series_info['part_number']}"
]
},
'main_content': {
'sections': [
{
'title': f"Section 1: Part {series_info['part_number']} Overview",
'content': f"Detailed content for part {series_info['part_number']}",
'subsections': []
},
{
'title': f"Section 2: Part {series_info['part_number']} Details",
'content': "Specific details and information",
'subsections': []
}
]
},
'conclusion': {
'summary': f"Concluding thoughts for part {series_info['part_number']}",
'next_part': f"Preview of part {series_info['part_number'] + 1}" if series_info['part_number'] < series_info['total_parts'] else "Series conclusion"
}
},
'metadata': {
'series_info': series_info,
'platform': content_item.platforms[0].name if content_item.platforms else 'Unknown',
'content_type': content_item.content_type.name
}
}
return content
except Exception as e:
self.logger.error(f"Error generating series content: {str(e)}", exc_info=True)
return {}
@handle_calendar_error
def generate_headings(
self,
title: str,
content_type: ContentType,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Generate content headings using AI.
Args:
title: Content title
content_type: Type of content
context: Content context from gap analysis
Returns:
List of generated headings with metadata
"""
try:
# Get content gaps and opportunities
gaps = self.gap_analyzer.analyze_gaps(context.get('website_url', ''))
# Generate headings based on content type and gaps
prompt = self._create_heading_prompt(title, content_type, gaps)
headings = self._call_ai_model(prompt)
return self._format_headings(headings)
except Exception as e:
logger.error(f"Error generating headings: {str(e)}")
return []
@handle_calendar_error
def generate_subheadings(
self,
main_heading: Dict[str, Any],
content_type: ContentType,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Generate subheadings for a main heading.
Args:
main_heading: Main heading to generate subheadings for
content_type: Type of content
context: Content context
Returns:
List of generated subheadings
"""
try:
# Create prompt for subheading generation
prompt = self._create_subheading_prompt(
main_heading,
content_type,
context
)
# Generate subheadings
subheadings = self._call_ai_model(prompt)
return self._format_subheadings(subheadings)
except Exception as e:
logger.error(f"Error generating subheadings: {str(e)}")
return []
@handle_calendar_error
def generate_key_points(
self,
title: str,
content_type: ContentType,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Generate key points for content.
Args:
title: Content title
content_type: Type of content
context: Content context
Returns:
List of key points with supporting information
"""
try:
# Generate title and meta description for SEO context
seo_title = self.title_generator(title)
meta_desc = self.meta_generator(title)
# Create prompt for key points
prompt = self._create_key_points_prompt(
title,
content_type,
{'title': seo_title, 'meta_description': meta_desc},
context
)
# Generate key points
points = self._call_ai_model(prompt)
return self._format_key_points(points)
except Exception as e:
logger.error(f"Error generating key points: {str(e)}")
return []
@handle_calendar_error
def generate_content_flow(
self,
title: str,
content_type: ContentType,
outline: Dict[str, Any]
) -> Dict[str, Any]:
"""
Generate content flow and structure.
Args:
title: Content title
content_type: Type of content
outline: Content outline with headings and key points
Returns:
Dictionary containing content flow and structure
"""
try:
# Create prompt for content flow
prompt = self._create_flow_prompt(title, content_type, outline)
# Generate content flow
flow = self._call_ai_model(prompt)
return self._format_content_flow(flow)
except Exception as e:
logger.error(f"Error generating content flow: {str(e)}")
return {}
def _create_heading_prompt(
self,
title: str,
content_type: ContentType,
gaps: Dict[str, Any]
) -> str:
"""Create prompt for heading generation."""
return f"""
Generate main headings for a {content_type.value} titled "{title}".
Consider the following content gaps and opportunities:
{json.dumps(gaps, indent=2)}
For each heading, provide:
1. Title
2. Level (1 for main headings)
3. Key keywords to include
4. Brief summary of what this section should cover
Format the response as a JSON array of heading objects.
"""
def _create_subheading_prompt(
self,
main_heading: Dict[str, Any],
content_type: ContentType,
context: Dict[str, Any]
) -> str:
"""Create prompt for subheading generation."""
return f"""
Generate subheadings for the main heading "{main_heading['title']}"
in a {content_type.value}.
Main heading details:
{json.dumps(main_heading, indent=2)}
For each subheading, provide:
1. Title
2. Level (2 for subheadings)
3. Key keywords to include
4. Brief summary of what this subsection should cover
Format the response as a JSON array of subheading objects.
"""
def _create_key_points_prompt(
self,
title: str,
content_type: ContentType,
seo_data: Dict[str, Any],
context: Dict[str, Any]
) -> str:
"""Create prompt for key points generation."""
return f"""
Generate key points for a {content_type.value} titled "{title}".
SEO Requirements:
{json.dumps(seo_data, indent=2)}
For each key point, provide:
1. Main point
2. Importance level (high/medium/low)
3. Supporting evidence or examples
4. Related keywords to include
Format the response as a JSON array of key point objects.
"""
def _create_flow_prompt(
self,
title: str,
content_type: ContentType,
outline: Dict[str, Any]
) -> str:
"""Create prompt for content flow generation."""
return f"""
Generate content flow and structure for a {content_type.value} titled "{title}".
Content Outline:
{json.dumps(outline, indent=2)}
Provide:
1. Introduction structure
2. Main sections flow
3. Conclusion approach
4. Transition points between sections
5. Content pacing recommendations
Format the response as a JSON object with these sections.
"""
def _call_ai_model(self, prompt: str) -> Any:
"""
Call the AI model with the given prompt.
Args:
prompt: The prompt to send to the AI model
Returns:
The AI model's response, parsed as JSON
"""
try:
# Call the AI model
response = llm_text_gen(
prompt=prompt,
max_tokens=1000,
temperature=0.7,
top_p=0.9,
frequency_penalty=0.5,
presence_penalty=0.5
)
# Parse the response as JSON
try:
return json.loads(response)
except json.JSONDecodeError as e:
logger.error(f"Error parsing AI response as JSON: {str(e)}")
logger.error(f"Raw response: {response}")
return {}
except Exception as e:
logger.error(f"Error calling AI model: {str(e)}")
return {}
def _format_headings(self, headings: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Format and validate generated headings."""
formatted = []
for heading in headings:
formatted.append({
'title': heading.get('title', ''),
'level': heading.get('level', 1),
'keywords': heading.get('keywords', []),
'summary': heading.get('summary', '')
})
return formatted
def _format_subheadings(self, subheadings: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Format and validate generated subheadings."""
formatted = []
for subheading in subheadings:
formatted.append({
'title': subheading.get('title', ''),
'level': subheading.get('level', 2),
'keywords': subheading.get('keywords', []),
'summary': subheading.get('summary', '')
})
return formatted
def _format_key_points(self, points: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Format and validate generated key points."""
formatted = []
for point in points:
formatted.append({
'point': point.get('point', ''),
'importance': point.get('importance', 'medium'),
'supporting_evidence': point.get('evidence', []),
'related_keywords': point.get('keywords', [])
})
return formatted
def _format_content_flow(self, flow: Dict[str, Any]) -> Dict[str, Any]:
"""Format and validate generated content flow."""
return {
'introduction': flow.get('introduction', {}),
'main_sections': flow.get('main_sections', []),
'conclusion': flow.get('conclusion', {}),
'transitions': flow.get('transitions', []),
'content_pacing': flow.get('pacing', {})
}
def generate_ai_suggestions(
self,
content_type: str,
topic: str,
audience: str,
goals: List[str],
tone: str,
length: str,
model_settings: Dict[str, Any],
style_preferences: List[str],
seo_preferences: Dict[str, Any],
platform_settings: Dict[str, Any],
platform: str
) -> List[Dict[str, Any]]:
"""
Generate AI content suggestions based on input parameters.
Args:
content_type: Type of content to generate
topic: Main topic or subject
audience: Target audience
goals: List of content goals
tone: Desired tone
length: Content length
model_settings: AI model settings
style_preferences: Style preferences
seo_preferences: SEO preferences
platform_settings: Platform-specific settings
platform: Target platform
Returns:
List of generated content suggestions
"""
try:
self.logger.info(f"Generating AI suggestions for topic: {topic}")
# Create a comprehensive prompt for content generation
prompt = f"""Generate content suggestions for the following parameters:
Content Type: {content_type}
Topic: {topic}
Target Audience: {audience}
Goals: {', '.join(goals)}
Tone: {tone}
Length: {length}
Style Preferences:
- Creativity Level: {model_settings['Creativity Level']}
- Formality Level: {model_settings['Formality Level']}
- Style Elements: {', '.join(style_preferences)}
SEO Preferences:
- Keyword Density: {seo_preferences['Keyword Density']}%
- Internal Linking: {'Enabled' if seo_preferences['Internal Linking'] else 'Disabled'}
- External Linking: {'Enabled' if seo_preferences['External Linking'] else 'Disabled'}
Platform Settings:
- Platform: {platform}
- Platform-specific requirements: {', '.join(platform_settings)}
Please generate 3 different content suggestions. Format your response as a valid JSON object with the following structure:
{{
"suggestions": [
{{
"title": "string",
"introduction": "string",
"key_points": ["string"],
"main_sections": [
{{
"title": "string",
"content": "string",
"engagement_elements": ["string"],
"seo_elements": ["string"]
}}
],
"conclusion": "string",
"seo_elements": ["string"],
"platform_optimizations": ["string"],
"engagement_strategies": ["string"],
"content_metrics": {{
"estimated_read_time": "string",
"word_count": "number",
"keyword_density": "number",
"engagement_score": "number"
}}
}}
]
}}
IMPORTANT: Your response must be a valid JSON object. Do not include any text before or after the JSON object."""
# Define JSON structure for validation
json_struct = {
"type": "object",
"properties": {
"suggestions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {"type": "string"},
"introduction": {"type": "string"},
"key_points": {"type": "array", "items": {"type": "string"}},
"main_sections": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {"type": "string"},
"content": {"type": "string"},
"engagement_elements": {"type": "array", "items": {"type": "string"}},
"seo_elements": {"type": "array", "items": {"type": "string"}}
}
}
},
"conclusion": {"type": "string"},
"seo_elements": {"type": "array", "items": {"type": "string"}},
"platform_optimizations": {"type": "array", "items": {"type": "string"}},
"engagement_strategies": {"type": "array", "items": {"type": "string"}},
"content_metrics": {
"type": "object",
"properties": {
"estimated_read_time": {"type": "string"},
"word_count": {"type": "number"},
"keyword_density": {"type": "number"},
"engagement_score": {"type": "number"}
}
}
}
}
}
}
}
# Generate content using llm_text_gen with JSON structure
generated_content = llm_text_gen(prompt, json_struct=json_struct)
if not generated_content:
raise ValueError("Failed to generate content suggestions")
# Parse the generated content
try:
# If generated_content is already a dict, use it directly
if isinstance(generated_content, dict):
content_data = generated_content
else:
# Try to parse as JSON string
content_data = json.loads(generated_content)
return self._format_suggestions(
content_data,
content_type,
audience,
goals,
tone,
length,
model_settings,
seo_preferences,
platform
)
except json.JSONDecodeError as e:
self.logger.error(f"Error parsing generated content: {str(e)}")
# Try to extract JSON from the response if it's wrapped in other text
try:
# Find the first '{' and last '}'
start = generated_content.find('{')
end = generated_content.rfind('}') + 1
if start >= 0 and end > start:
json_str = generated_content[start:end]
content_data = json.loads(json_str)
return self._format_suggestions(
content_data,
content_type,
audience,
goals,
tone,
length,
model_settings,
seo_preferences,
platform
)
except Exception as e2:
self.logger.error(f"Error extracting JSON from response: {str(e2)}")
raise ValueError("Failed to parse generated content")
except Exception as e:
self.logger.error(f"Error generating AI suggestions: {str(e)}", exc_info=True)
raise
def _format_suggestions(
self,
content_data: Dict[str, Any],
content_type: str,
audience: str,
goals: List[str],
tone: str,
length: str,
model_settings: Dict[str, Any],
seo_preferences: Dict[str, Any],
platform: str
) -> List[Dict[str, Any]]:
"""Format and process suggestions from content data."""
suggestions = []
for suggestion in content_data.get('suggestions', []):
formatted_suggestion = {
'title': suggestion.get('title', ''),
'type': content_type,
'platform': platform,
'audience': audience,
'impact': f"High impact for {', '.join(goals)}",
'preview': suggestion.get('introduction', ''),
'style_elements': [
f"Tone: {tone}",
f"Length: {length}",
f"Creativity: {model_settings['Creativity Level']}",
f"Formality: {model_settings['Formality Level']}"
],
'seo_elements': [
f"Keyword Density: {seo_preferences['Keyword Density']}%",
"Internal Linking: Enabled" if seo_preferences['Internal Linking'] else "Internal Linking: Disabled",
"External Linking: Enabled" if seo_preferences['External Linking'] else "External Linking: Disabled"
],
'engagement_score': f"{85 + len(suggestions)*5}%",
'reach': 'High',
'conversion': f"{3.5 + len(suggestions)*0.5}%",
'seo_impact': 'Strong',
'platform_optimizations': suggestion.get('platform_optimizations', []),
'variations': [
"Alternative headline",
"Different content angle",
"Alternative format"
],
'seo_recommendations': suggestion.get('seo_elements', []),
'media_suggestions': [
"Featured image",
"Supporting graphics",
"Social media visuals"
]
}
suggestions.append(formatted_suggestion)
return suggestions

View File

@@ -0,0 +1,196 @@
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional
import logging
import sys
import json
import os
from ..integrations.seo_tools import SEOToolsIntegration
from ..integrations.gap_analyzer import GapAnalyzerIntegration
from ..models.calendar import Calendar, ContentItem
from ..utils.date_utils import calculate_publish_dates
from ..utils.error_handling import handle_calendar_error
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler('content_calendar_debug.log', mode='a')
]
)
logger = logging.getLogger(__name__)
CALENDAR_JSON_PATH = "calendar_data.json"
class CalendarManager:
"""
Main calendar management system that coordinates content planning,
scheduling, and optimization.
"""
def __init__(self):
"""Initialize calendar manager."""
self.logger = logging.getLogger('content_calendar.manager')
self.logger.info("Initializing CalendarManager")
self.seo_tools = SEOToolsIntegration()
self.gap_analyzer = GapAnalyzerIntegration()
self._calendar: Optional[Calendar] = None
self.logger.info("CalendarManager initialized successfully")
@handle_calendar_error
def create_calendar(
self,
start_date: datetime,
duration: str, # 'weekly', 'monthly', 'quarterly'
platforms: List[str],
website_url: str
) -> Calendar:
"""
Create a new content calendar based on content gap analysis and SEO requirements.
Args:
start_date: When the calendar should begin
duration: How long the calendar should span
platforms: List of platforms to create content for
website_url: URL of the website to analyze
Returns:
Calendar object containing the content schedule
"""
self.logger.info(f"Creating new calendar for {website_url}")
self.logger.debug(f"Parameters: start_date={start_date}, duration={duration}, platforms={platforms}")
try:
# 1. Analyze content gaps
self.logger.info("Analyzing content gaps")
gap_analysis = self.gap_analyzer.analyze_gaps(website_url)
# 2. Generate topics based on gaps
self.logger.info("Generating topics from gap analysis")
topics = self._generate_topics(gap_analysis, platforms)
# 3. Calculate publish dates
self.logger.info("Calculating publish dates")
schedule = calculate_publish_dates(
topics=topics,
start_date=start_date,
duration=duration
)
# 4. Create calendar
self.logger.info("Creating calendar object")
self._calendar = Calendar(
start_date=start_date,
duration=duration,
platforms=platforms,
schedule=schedule
)
self.logger.info("Calendar created successfully")
return self._calendar
except Exception as e:
self.logger.error(f"Error creating calendar: {str(e)}", exc_info=True)
raise
def _generate_topics(
self,
gap_analysis: Dict[str, Any],
platforms: List[str]
) -> List[ContentItem]:
"""
Generate content topics based on gap analysis and platform requirements.
"""
topics = []
for gap in gap_analysis['gaps']:
# Generate topic using AI
topic = self._generate_topic_from_gap(gap, platforms)
# Optimize for SEO
optimized_topic = self._optimize_topic(topic)
topics.append(optimized_topic)
return topics
def _generate_topic_from_gap(
self,
gap: Dict[str, Any],
platforms: List[str]
) -> ContentItem:
"""
Generate a specific topic based on a content gap.
"""
# Use existing AI tools to generate topic
topic_data = {
'title': self._generate_title(gap),
'description': self._generate_description(gap),
'keywords': gap.get('keywords', []),
'platforms': platforms,
'content_type': self._determine_content_type(gap, platforms)
}
return ContentItem(**topic_data)
def _optimize_topic(self, topic: ContentItem) -> ContentItem:
"""
Optimize a topic for SEO using existing tools.
"""
# Optimize title
topic.title = self.seo_tools.optimize_title(topic.title)
# Generate meta description
topic.meta_description = self.seo_tools.generate_meta_description(
topic.description
)
# Add structured data
topic.structured_data = self.seo_tools.generate_structured_data(
topic.content_type
)
return topic
def get_calendar(self) -> Optional[Calendar]:
"""
Get the current calendar.
"""
self.logger.debug("Getting current calendar")
return self._calendar
def update_calendar(self, calendar: Calendar) -> None:
"""
Update the current calendar.
"""
self._calendar = calendar
def export_calendar(self) -> Optional[Dict[str, Any]]:
"""Export the current calendar."""
self.logger.info("Exporting calendar")
if not self._calendar:
self.logger.warning("No calendar to export")
return None
try:
calendar_data = self._calendar.export()
self.logger.info("Calendar exported successfully")
return calendar_data
except Exception as e:
self.logger.error(f"Error exporting calendar: {str(e)}", exc_info=True)
return None
def save_calendar_to_json(self):
calendar = self.get_calendar()
if calendar:
with open(CALENDAR_JSON_PATH, "w") as f:
json.dump(calendar.to_dict(), f, indent=2, default=str)
def load_calendar_from_json(self):
from lib.ai_seo_tools.content_calendar.models.calendar import Calendar
if os.path.exists(CALENDAR_JSON_PATH):
with open(CALENDAR_JSON_PATH, "r") as f:
data = json.load(f)
self._calendar = Calendar.from_dict(data)

View File

@@ -0,0 +1,151 @@
from typing import Dict, List, Any, Optional
import logging
from pathlib import Path
import sys
# Add parent directory to path to import existing tools
parent_dir = str(Path(__file__).parent.parent.parent.parent)
if parent_dir not in sys.path:
sys.path.append(parent_dir)
from lib.ai_seo_tools.content_calendar.models.calendar import ContentType, ContentItem, Platform
from lib.ai_seo_tools.content_calendar.utils.error_handling import handle_calendar_error
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from lib.ai_seo_tools.content_gap_analysis.main import ContentGapAnalysis
from lib.ai_seo_tools.content_title_generator import ai_title_generator
from lib.ai_seo_tools.meta_desc_generator import metadesc_generator_main
from .ai_generator import AIGenerator
logger = logging.getLogger(__name__)
class ContentBriefGenerator:
"""
Generates comprehensive content briefs using AI-powered analysis.
"""
def __init__(self):
self.logger = logging.getLogger('content_calendar.content_brief')
self.logger.info("Initializing ContentBriefGenerator")
self._setup_logging()
self._load_ai_tools()
def _setup_logging(self):
"""Configure logging for content brief generator."""
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
def _load_ai_tools(self):
"""Load and initialize AI tools."""
try:
# Initialize AI tools
self.gap_analyzer = ContentGapAnalysis()
self.title_generator = ai_title_generator
self.meta_generator = metadesc_generator_main
self.ai_generator = AIGenerator()
except Exception as e:
logger.error(f"Error loading AI tools: {str(e)}")
raise
@handle_calendar_error
def generate_brief(
self,
content_item: ContentItem,
target_audience: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Generate a comprehensive content brief.
Args:
content_item: Content item to generate brief for
target_audience: Optional target audience data
Returns:
Dictionary containing the content brief
"""
try:
logger.info(f"Generating content brief for: {content_item.title}")
# Generate content outline
outline = self._generate_outline(content_item)
# Generate key points
key_points = self.ai_generator.generate_key_points(
title=content_item.title,
content_type=content_item.content_type,
context=content_item.context
)
# Generate content flow
content_flow = self.ai_generator.generate_content_flow(
title=content_item.title,
content_type=content_item.content_type,
outline=outline
)
# Compile the brief
brief = {
'title': content_item.title,
'content_type': content_item.content_type.value,
'outline': outline,
'key_points': key_points,
'content_flow': content_flow,
'target_audience': target_audience or {},
'seo_data': content_item.seo_data,
'platform_specs': content_item.platform_specs
}
logger.info("Content brief generated successfully")
return brief
except Exception as e:
logger.error(f"Error generating content brief: {str(e)}")
raise
def _generate_outline(
self,
content_item: ContentItem
) -> Dict[str, Any]:
"""
Generate content outline with headings and subheadings.
Args:
content_item: Content item to generate outline for
Returns:
Dictionary containing the content outline
"""
try:
# Generate main headings
main_headings = self.ai_generator.generate_headings(
title=content_item.title,
content_type=content_item.content_type,
context=content_item.context
)
# Generate subheadings for each main heading
subheadings = {}
for heading in main_headings:
heading_subheadings = self.ai_generator.generate_subheadings(
main_heading=heading,
content_type=content_item.content_type,
context=content_item.context
)
subheadings[heading['title']] = heading_subheadings
return {
'main_headings': main_headings,
'subheadings': subheadings
}
except Exception as e:
logger.error(f"Error generating outline: {str(e)}")
return {
'main_headings': [],
'subheadings': {}
}

View File

@@ -0,0 +1,323 @@
from typing import Dict, List, Any, Optional
import logging
from pathlib import Path
import sys
# Add parent directory to path to import existing tools
parent_dir = str(Path(__file__).parent.parent.parent.parent)
if parent_dir not in sys.path:
sys.path.append(parent_dir)
from ..models.calendar import ContentItem, ContentType
from ..utils.error_handling import handle_calendar_error
from lib.ai_seo_tools.content_gap_analysis.main import ContentGapAnalysis
from lib.ai_seo_tools.content_title_generator import ai_title_generator
from lib.ai_seo_tools.meta_desc_generator import metadesc_generator_main
logger = logging.getLogger(__name__)
class ContentGenerator:
"""
AI-powered content generation for content briefs.
"""
def __init__(self):
self.logger = logging.getLogger('content_calendar.content_generator')
self.logger.info("Initializing ContentGenerator")
self._setup_logging()
self._load_ai_tools()
def _setup_logging(self):
"""Configure logging for content generator."""
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
def _load_ai_tools(self):
"""Load and initialize AI tools."""
try:
# Initialize AI tools
self.gap_analyzer = ContentGapAnalysis()
self.title_generator = ai_title_generator
self.meta_generator = metadesc_generator_main
except Exception as e:
logger.error(f"Error loading AI tools: {str(e)}")
raise
@handle_calendar_error
def generate_headings(
self,
content_item: ContentItem,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Generate main headings for content.
Args:
content_item: Content item to generate headings for
context: Content context from gap analysis
Returns:
List of main headings with metadata
"""
try:
# Use AI to generate headings based on content type and context
headings = self._generate_ai_headings(
title=content_item.title,
content_type=content_item.content_type,
context=context
)
# Format and validate headings
formatted_headings = []
for heading in headings:
formatted_heading = {
'title': heading['title'],
'level': heading.get('level', 1),
'keywords': heading.get('keywords', []),
'summary': heading.get('summary', '')
}
formatted_headings.append(formatted_heading)
return formatted_headings
except Exception as e:
logger.error(f"Error generating headings: {str(e)}")
return []
@handle_calendar_error
def generate_subheadings(
self,
content_item: ContentItem,
main_headings: List[Dict[str, Any]],
context: Dict[str, Any]
) -> Dict[str, List[Dict[str, Any]]]:
"""
Generate subheadings for each main heading.
Args:
content_item: Content item to generate subheadings for
main_headings: List of main headings
context: Content context from gap analysis
Returns:
Dictionary mapping main headings to their subheadings
"""
try:
subheadings = {}
for heading in main_headings:
# Generate subheadings for each main heading
heading_subheadings = self._generate_ai_subheadings(
main_heading=heading,
content_type=content_item.content_type,
context=context
)
# Format and validate subheadings
formatted_subheadings = []
for subheading in heading_subheadings:
formatted_subheading = {
'title': subheading['title'],
'level': subheading.get('level', 2),
'keywords': subheading.get('keywords', []),
'summary': subheading.get('summary', '')
}
formatted_subheadings.append(formatted_subheading)
subheadings[heading['title']] = formatted_subheadings
return subheadings
except Exception as e:
logger.error(f"Error generating subheadings: {str(e)}")
return {}
@handle_calendar_error
def generate_key_points(
self,
content_item: ContentItem,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Generate key points for the content.
Args:
content_item: Content item to generate key points for
context: Content context from gap analysis
Returns:
List of key points with supporting information
"""
try:
# Generate key points using AI
key_points = self._generate_ai_key_points(
title=content_item.title,
content_type=content_item.content_type,
context=context
)
# Format and validate key points
formatted_points = []
for point in key_points:
formatted_point = {
'point': point['point'],
'importance': point.get('importance', 'medium'),
'supporting_evidence': point.get('evidence', []),
'related_keywords': point.get('keywords', [])
}
formatted_points.append(formatted_point)
return formatted_points
except Exception as e:
logger.error(f"Error generating key points: {str(e)}")
return []
@handle_calendar_error
def generate_content_flow(
self,
content_item: ContentItem,
outline: Dict[str, Any]
) -> Dict[str, Any]:
"""
Generate content flow and structure.
Args:
content_item: Content item to generate flow for
outline: Content outline with headings and key points
Returns:
Dictionary containing content flow and structure
"""
try:
# Generate content flow using AI
flow = self._generate_ai_content_flow(
title=content_item.title,
content_type=content_item.content_type,
outline=outline
)
return {
'introduction': flow.get('introduction', {}),
'main_sections': flow.get('main_sections', []),
'conclusion': flow.get('conclusion', {}),
'transitions': flow.get('transitions', []),
'content_pacing': flow.get('pacing', {})
}
except Exception as e:
logger.error(f"Error generating content flow: {str(e)}")
return {}
def _generate_ai_headings(
self,
title: str,
content_type: ContentType,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Use AI to generate content headings.
"""
# TODO: Implement AI heading generation
# This would use the existing AI tools to generate headings
return []
def _generate_ai_subheadings(
self,
main_heading: Dict[str, Any],
content_type: ContentType,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Use AI to generate subheadings.
"""
# TODO: Implement AI subheading generation
return []
def _generate_ai_key_points(
self,
title: str,
content_type: ContentType,
context: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Use AI to generate key points.
"""
# TODO: Implement AI key point generation
return []
def _generate_ai_content_flow(
self,
title: str,
content_type: ContentType,
outline: Dict[str, Any]
) -> Dict[str, Any]:
"""
Use AI to generate content flow.
"""
# TODO: Implement AI content flow generation
return {}
def generate_variation(self, content: Dict[str, Any], variation_type: str) -> Dict[str, Any]:
"""Generate a variation of the given content.
Args:
content: Original content to vary
variation_type: Type of variation to generate ('tone', 'length', 'style', etc.)
Returns:
Dictionary containing the varied content
"""
try:
self.logger.info(f"Generating {variation_type} variation for content")
# Generate variation based on type
variation = {
'title': f"{content.get('title', '')} - {variation_type.title()} Variation",
'content_flow': {
'introduction': {
'summary': f"Varied introduction for {content.get('title', '')}",
'key_points': [
f"Varied key point 1 for {variation_type}",
f"Varied key point 2 for {variation_type}",
f"Varied key point 3 for {variation_type}"
]
},
'main_content': {
'sections': [
{
'title': f"Varied Section 1: {variation_type.title()} Approach",
'content': f"Varied content for {variation_type}",
'subsections': []
},
{
'title': f"Varied Section 2: {variation_type.title()} Details",
'content': "Varied details and information",
'subsections': []
}
]
},
'conclusion': {
'summary': f"Varied conclusion for {variation_type}",
'call_to_action': "Varied call to action"
}
},
'metadata': {
'variation_type': variation_type,
'original_content': content.get('title', ''),
'platform': content.get('metadata', {}).get('platform', 'Unknown'),
'content_type': content.get('metadata', {}).get('content_type', 'Unknown')
}
}
return variation
except Exception as e:
self.logger.error(f"Error generating variation: {str(e)}")
return {}

View File

@@ -0,0 +1,80 @@
from datetime import datetime
from typing import List, Dict, Any
from ..core.calendar_manager import CalendarManager
from ..models.calendar import ContentType, Platform
def create_content_calendar(
website_url: str,
start_date: datetime,
duration: str,
platforms: List[str]
) -> Dict[str, Any]:
"""
Example of creating a content calendar.
Args:
website_url: URL of the website to analyze
start_date: When to start the calendar
duration: How long the calendar should span
platforms: List of platforms to create content for
Returns:
Dictionary containing the calendar data
"""
# Initialize calendar manager
calendar_manager = CalendarManager()
# Create calendar
calendar = calendar_manager.create_calendar(
start_date=start_date,
duration=duration,
platforms=platforms,
website_url=website_url
)
# Export calendar
calendar_data = calendar_manager.export_calendar()
return calendar_data
def main():
"""Example usage of the content calendar system."""
# Example parameters
website_url = "https://example.com"
start_date = datetime.now()
duration = "monthly"
platforms = [
Platform.WEBSITE.value,
Platform.FACEBOOK.value,
Platform.TWITTER.value,
Platform.LINKEDIN.value
]
try:
# Create calendar
calendar_data = create_content_calendar(
website_url=website_url,
start_date=start_date,
duration=duration,
platforms=platforms
)
# Print calendar summary
print("\nContent Calendar Summary:")
print(f"Duration: {calendar_data['duration']}")
print(f"Platforms: {', '.join(calendar_data['platforms'])}")
print("\nScheduled Content:")
for date, items in calendar_data['schedule'].items():
print(f"\n{date}:")
for item in items:
print(f"- {item['title']} ({item['content_type']})")
print(f" Platforms: {', '.join(item['platforms'])}")
print(f" Status: {item['status']}")
except Exception as e:
print(f"Error creating calendar: {str(e)}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,138 @@
from datetime import datetime
from typing import Dict, Any
from ..models.calendar import ContentItem, ContentType, Platform, SEOData
from ..core.content_brief import ContentBriefGenerator
def create_content_brief(
title: str,
content_type: ContentType,
platforms: list[Platform],
website_url: str,
target_audience: Dict[str, Any]
) -> Dict[str, Any]:
"""
Create a content brief for the given content.
Args:
title: Content title
content_type: Type of content
platforms: List of platforms to publish on
website_url: Website URL for context
target_audience: Target audience information
Returns:
Dictionary containing the content brief
"""
# Create content item
content_item = ContentItem(
id=f"content-{datetime.now().strftime('%Y%m%d%H%M%S')}",
title=title,
description=f"Content brief for {title}",
content_type=content_type,
platforms=platforms,
publish_date=datetime.now(),
seo_data=SEOData(
keywords=[], # Will be generated by SEO tools
meta_description="", # Will be generated by SEO tools
structured_data={}
),
platform_specs={}, # Will be generated based on platforms
context={
"website_url": website_url,
"target_audience": target_audience.get("demographics", {}).get("profession", ""),
"content_goals": ["educate", "generate leads"]
}
)
# Initialize content brief generator
generator = ContentBriefGenerator()
# Generate brief
brief = generator.generate_brief(
content_item=content_item,
target_audience=target_audience
)
return brief
def main():
"""Example usage of content brief generation."""
# Example content details
title = "10 Ways to Improve Your SEO Strategy"
content_type = ContentType.BLOG_POST
platforms = [Platform.WEBSITE, Platform.LINKEDIN]
website_url = "https://example.com"
# Example target audience
target_audience = {
"demographics": {
"age_range": "25-45",
"profession": "digital marketers",
"experience_level": "intermediate"
},
"interests": [
"SEO",
"content marketing",
"digital strategy",
"search engine optimization"
],
"pain_points": [
"low search rankings",
"poor content performance",
"lack of organic traffic",
"difficulty in keyword research"
],
"goals": [
"improve search rankings",
"increase organic traffic",
"generate more leads",
"build brand authority"
]
}
try:
# Generate content brief
brief = create_content_brief(
title=title,
content_type=content_type,
platforms=platforms,
website_url=website_url,
target_audience=target_audience
)
# Print brief summary
print("\nContent Brief Summary:")
print(f"Title: {brief['title']}")
print(f"Content Type: {brief['content_type']}")
print("\nOutline:")
for heading in brief['outline']['main_headings']:
print(f"\n- {heading['title']}")
print(f" Keywords: {', '.join(heading['keywords'])}")
print(f" Summary: {heading['summary']}")
# Print subheadings
subheadings = brief['outline']['subheadings'].get(heading['title'], [])
for subheading in subheadings:
print(f" - {subheading['title']}")
print(f" Keywords: {', '.join(subheading['keywords'])}")
print("\nKey Points:")
for point in brief['key_points']:
print(f"\n- {point['point']}")
print(f" Importance: {point['importance']}")
print(f" Evidence: {', '.join(point['supporting_evidence'])}")
print("\nContent Flow:")
flow = brief['content_flow']
print(f"Introduction: {flow['introduction'].get('summary', '')}")
print(f"Main Sections: {len(flow['main_sections'])} sections")
print(f"Conclusion: {flow['conclusion'].get('summary', '')}")
print(f"Transitions: {len(flow['transitions'])} transition points")
except Exception as e:
print(f"Error generating content brief: {str(e)}")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,196 @@
import logging
from datetime import datetime, timedelta
from typing import Dict, Any, List
from ..integrations.integration_manager import IntegrationManager
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def create_cross_platform_content(
title: str,
content: str,
platforms: List[str],
content_type: str,
target_audience: Dict[str, Any],
industry: str,
keywords: List[str]
) -> Dict[str, Any]:
"""Create and optimize content for multiple platforms."""
try:
# Initialize integration manager
integration_manager = IntegrationManager()
# Prepare content item
content_item = {
'title': title,
'content': content,
'content_type': content_type,
'keywords': keywords,
'target_audience': target_audience,
'industry': industry
}
# Get platform suggestions
suggestions = integration_manager.get_platform_suggestions(
content=content_item,
platforms=platforms
)
# Validate content for each platform
validation_results = {}
for platform in platforms:
validation = integration_manager.validate_platform_content(
content=content_item,
platform=platform
)
validation_results[platform] = validation
# Optimize content for each platform
optimized_content = integration_manager.optimize_cross_platform_content(
content=content_item,
platforms=platforms
)
return {
'original_content': content_item,
'platform_suggestions': suggestions,
'validation_results': validation_results,
'optimized_content': optimized_content
}
except Exception as e:
logger.error(f"Error creating cross-platform content: {str(e)}")
return {
'error': str(e)
}
def create_content_calendar(
start_date: datetime,
end_date: datetime,
platforms: List[str],
content_types: List[str],
target_audience: Dict[str, Any],
industry: str,
keywords: List[str]
) -> Dict[str, Any]:
"""Create a cross-platform content calendar."""
try:
# Initialize integration manager
integration_manager = IntegrationManager()
# Create calendar
calendar = integration_manager.create_cross_platform_calendar(
start_date=start_date,
end_date=end_date,
platforms=platforms,
content_types=content_types,
target_audience=target_audience,
industry=industry,
keywords=keywords
)
return calendar
except Exception as e:
logger.error(f"Error creating content calendar: {str(e)}")
return {
'error': str(e)
}
def main():
"""Main function to demonstrate integration manager usage."""
# Example content details
title = "The Future of AI in Content Marketing"
content = """
Artificial Intelligence is revolutionizing the way we approach content marketing.
From automated content generation to personalized recommendations, AI tools are
helping marketers create more engaging and effective content strategies.
Key points:
1. AI-powered content generation
2. Personalized content recommendations
3. Automated content optimization
4. Data-driven content strategy
5. Future trends in AI marketing
"""
# Platform and content settings
platforms = ['instagram', 'twitter', 'linkedin', 'blog', 'facebook']
content_type = 'article'
target_audience = {
'age_range': '25-34',
'interests': ['technology', 'marketing', 'AI'],
'location': 'global',
'profession': 'marketing professionals'
}
industry = 'technology'
keywords = ['AI', 'content marketing', 'automation', 'personalization']
# Create cross-platform content
logger.info("Creating cross-platform content...")
content_result = create_cross_platform_content(
title=title,
content=content,
platforms=platforms,
content_type=content_type,
target_audience=target_audience,
industry=industry,
keywords=keywords
)
# Print content results
logger.info("\nCross-Platform Content Results:")
logger.info("===============================")
# Print platform suggestions
logger.info("\nPlatform Suggestions:")
for platform, suggestions in content_result['platform_suggestions'].items():
logger.info(f"\n{platform.upper()}:")
for key, value in suggestions.items():
logger.info(f" {key}: {value}")
# Print validation results
logger.info("\nValidation Results:")
for platform, validation in content_result['validation_results'].items():
logger.info(f"\n{platform.upper()}:")
logger.info(f" Valid: {validation['is_valid']}")
if not validation['is_valid']:
logger.info(f" Error: {validation.get('error', 'N/A')}")
# Print optimized content
logger.info("\nOptimized Content:")
for platform, optimized in content_result['optimized_content'].items():
logger.info(f"\n{platform.upper()}:")
for key, value in optimized.items():
logger.info(f" {key}: {value}")
# Create content calendar
logger.info("\nCreating content calendar...")
start_date = datetime.now()
end_date = start_date + timedelta(days=30)
calendar_result = create_content_calendar(
start_date=start_date,
end_date=end_date,
platforms=platforms,
content_types=[content_type],
target_audience=target_audience,
industry=industry,
keywords=keywords
)
# Print calendar results
logger.info("\nContent Calendar Results:")
logger.info("========================")
# Print platform calendars
logger.info("\nPlatform Calendars:")
for platform, calendar in calendar_result['platform_calendars'].items():
logger.info(f"\n{platform.upper()}:")
logger.info(f" Content Items: {len(calendar['content_items'])}")
for item in calendar['content_items']:
logger.info(f" - {item['original_item']['title']}")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,142 @@
from typing import Dict, Any
from datetime import datetime
from ..integrations.platform_adapters import UnifiedPlatformAdapter
def create_platform_content(
title: str,
content: str,
platforms: list,
context: Dict[str, Any] = None
) -> Dict[str, Any]:
"""
Create platform-specific content using the UnifiedPlatformAdapter.
Args:
title: The title of the content
content: The main content to be adapted
platforms: List of platforms to adapt content for
context: Additional context for content adaptation
Returns:
Dict containing adapted content for each platform
"""
# Initialize the platform adapter
adapter = UnifiedPlatformAdapter()
# Prepare base content
base_content = {
'title': title,
'content': content,
'keywords': ['content', 'marketing', 'social media'],
'tone': 'professional',
'cta': 'Learn More',
'audience': 'For All',
'language': 'English',
'industry': 'technology',
'word_count': 1000
}
# Adapt content for each platform
adapted_content = {}
for platform in platforms:
try:
platform_content = adapter.adapt_content(
content=base_content,
platform=platform,
context=context
)
adapted_content[platform] = platform_content
except Exception as e:
print(f"Error adapting content for {platform}: {str(e)}")
adapted_content[platform] = {'error': str(e)}
return adapted_content
def main():
"""Example usage of platform content adaptation."""
# Example content
title = "The Future of AI in Content Marketing"
content = """
Artificial Intelligence is revolutionizing content marketing in unprecedented ways.
From automated content generation to personalized user experiences, AI is becoming
an indispensable tool for marketers. This article explores the latest trends and
innovations in AI-powered content marketing.
"""
# Example context
context = {
'target_audience': 'marketing professionals',
'campaign_goals': ['awareness', 'engagement', 'lead generation'],
'brand_voice': 'authoritative yet approachable',
'content_theme': 'technology and innovation'
}
# Platforms to adapt content for
platforms = ['instagram', 'twitter', 'linkedin', 'blog', 'facebook']
# Generate platform-specific content
adapted_content = create_platform_content(
title=title,
content=content,
platforms=platforms,
context=context
)
# Print results
print("\nPlatform-Specific Content Adaptation Results:")
print("=" * 50)
for platform, content in adapted_content.items():
print(f"\n{platform.upper()} Content:")
print("-" * 30)
if 'error' in content:
print(f"Error: {content['error']}")
continue
# Print platform-specific content
if platform == 'instagram':
print("\nCaptions:")
for caption in content['captions']:
print(f"- {caption}")
print("\nHashtags:")
print(content['hashtags'])
elif platform == 'twitter':
print("\nTweets:")
for tweet in content['tweets']:
print(f"- {tweet}")
print("\nThread Structure:")
print(content['thread_structure'])
elif platform == 'linkedin':
print("\nPost:")
print(content['post'])
print("\nEngagement Optimization:")
print(content['engagement_optimization'])
elif platform == 'blog':
print("\nPost:")
print(content['post'])
print("\nSEO Optimization:")
print(content['seo_optimization'])
elif platform == 'facebook':
print("\nPost:")
print(content['post'])
print("\nEngagement Optimization:")
print(content['engagement_optimization'])
# Print media suggestions
print("\nMedia Suggestions:")
for media in content['media_suggestions']:
print(f"- {media['type']}: {media['description']}")
# Print platform-specific recommendations
print("\nPlatform-Specific Recommendations:")
for key, value in content['platform_specific'].items():
print(f"- {key}: {value}")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,127 @@
"""
Gap analyzer integration for content calendar.
"""
import streamlit as st
from typing import Dict, Any, List, Optional
from loguru import logger
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer
from lib.ai_seo_tools.content_gap_analysis.main import ContentGapAnalysis
import asyncio
import sys
import os
import json
from datetime import datetime
# Configure logger for content calendar debugging
logger.remove() # Remove default handler
logger.add(
sys.stdout,
level="DEBUG",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan> | <yellow>{function}</yellow> | {message}",
filter=lambda record: "content_calendar" in record["name"].lower()
)
class GapAnalyzerIntegration:
"""Integrates content gap analysis with content calendar."""
def __init__(self):
"""Initialize the gap analyzer integration."""
self.gap_analyzer = ContentGapAnalysis()
logger.debug("GapAnalyzerIntegration initialized for content calendar")
def analyze_gaps(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""
Analyze content gaps.
Args:
data: Dictionary containing content data
Returns:
Dictionary containing gap analysis results
"""
try:
logger.debug(f"Starting gap analysis with data: {json.dumps(data, indent=2)}")
# Run gap analysis
results = self.gap_analyzer.analyze(data)
logger.debug(f"Gap analysis completed with results: {json.dumps(results, indent=2)}")
return results
except Exception as e:
error_msg = f"Error analyzing content gaps: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
'error': error_msg,
'gaps': [],
'recommendations': []
}
def get_topic_suggestions(
self,
gap_analysis: Dict[str, Any],
platform: str,
count: int = 5
) -> List[Dict[str, Any]]:
"""
Get topic suggestions for a specific platform based on gap analysis.
Args:
gap_analysis: Results from gap analysis
platform: Target platform for content
count: Number of suggestions to generate
Returns:
List of topic suggestions
"""
try:
logger.debug(f"Generating topic suggestions for platform: {platform}, count: {count}")
suggestions = []
for gap in gap_analysis.get('processed_gaps', []):
# Generate platform-specific topics
platform_topics = self.ai_processor.generate_platform_topics(
gap=gap,
platform=platform,
count=count
)
logger.debug(f"Generated topics for gap: {json.dumps(platform_topics, indent=2)}")
suggestions.extend(platform_topics)
logger.debug(f"Total suggestions generated: {len(suggestions)}")
return suggestions
except Exception as e:
logger.error(f"Error generating topic suggestions: {str(e)}")
return []
def analyze_topic_relevance(
self,
topic: Dict[str, Any],
gap_analysis: Dict[str, Any]
) -> Dict[str, Any]:
"""
Analyze how well a topic addresses content gaps.
Args:
topic: Topic to analyze
gap_analysis: Results from gap analysis
Returns:
Dictionary containing relevance analysis
"""
try:
logger.debug(f"Analyzing topic relevance: {json.dumps(topic, indent=2)}")
relevance = self.ai_processor.analyze_topic_relevance(
topic=topic,
gaps=gap_analysis.get('gaps', [])
)
logger.debug(f"Topic relevance analysis completed: {json.dumps(relevance, indent=2)}")
return relevance
except Exception as e:
logger.error(f"Error analyzing topic relevance: {str(e)}")
return {
'error': str(e),
'score': 0
}

View File

@@ -0,0 +1,196 @@
import logging
from typing import Dict, List, Any, Optional
from datetime import datetime, timedelta
from ..core.calendar_manager import CalendarManager
from ..core.content_brief import ContentBriefGenerator
from .platform_adapters import UnifiedPlatformAdapter
logger = logging.getLogger(__name__)
class IntegrationManager:
"""Manages integration between content calendar and platform adapters."""
def __init__(self):
"""Initialize the integration manager."""
self.calendar_manager = CalendarManager()
self.content_brief_generator = ContentBriefGenerator()
self.platform_adapter = UnifiedPlatformAdapter()
def create_cross_platform_calendar(
self,
start_date: datetime,
end_date: datetime,
platforms: List[str],
content_types: List[str],
target_audience: Optional[Dict[str, Any]] = None,
industry: Optional[str] = None,
keywords: Optional[List[str]] = None
) -> Dict[str, Any]:
"""Create a cross-platform content calendar."""
try:
# Generate base calendar
calendar = self.calendar_manager.create_calendar(
start_date=start_date,
end_date=end_date,
content_types=content_types,
target_audience=target_audience,
industry=industry,
keywords=keywords
)
# Adapt content for each platform
platform_calendars = {}
for platform in platforms:
platform_calendars[platform] = self._adapt_calendar_for_platform(
calendar=calendar,
platform=platform
)
return {
'base_calendar': calendar,
'platform_calendars': platform_calendars,
'metadata': {
'start_date': start_date,
'end_date': end_date,
'platforms': platforms,
'content_types': content_types,
'industry': industry,
'keywords': keywords
}
}
except Exception as e:
logger.error(f"Error creating cross-platform calendar: {str(e)}")
raise
def _adapt_calendar_for_platform(
self,
calendar: Dict[str, Any],
platform: str
) -> Dict[str, Any]:
"""Adapt calendar content for a specific platform."""
try:
adapted_calendar = {
'platform': platform,
'content_items': [],
'metadata': calendar.get('metadata', {})
}
# Adapt each content item
for item in calendar.get('content_items', []):
adapted_item = self._adapt_content_item(item, platform)
if adapted_item:
adapted_calendar['content_items'].append(adapted_item)
return adapted_calendar
except Exception as e:
logger.error(f"Error adapting calendar for platform {platform}: {str(e)}")
return {
'platform': platform,
'content_items': [],
'error': str(e)
}
def _adapt_content_item(
self,
item: Dict[str, Any],
platform: str
) -> Optional[Dict[str, Any]]:
"""Adapt a content item for a specific platform."""
try:
# Generate content brief if not exists
if 'brief' not in item:
item['brief'] = self.content_brief_generator.generate_brief(item)
# Adapt content for platform
adapted_content = self.platform_adapter.adapt_content(
content=item,
platform=platform
)
if adapted_content:
return {
'original_item': item,
'adapted_content': adapted_content,
'platform_specifics': self.platform_adapter.get_platform_specs(platform)
}
return None
except Exception as e:
logger.error(f"Error adapting content item for platform {platform}: {str(e)}")
return None
def get_platform_suggestions(
self,
content: Dict[str, Any],
platforms: List[str]
) -> Dict[str, Any]:
"""Get platform-specific suggestions for content."""
try:
suggestions = {}
for platform in platforms:
platform_suggestions = self.platform_adapter.get_platform_suggestions(
content=content,
platform=platform
)
if platform_suggestions:
suggestions[platform] = platform_suggestions
return suggestions
except Exception as e:
logger.error(f"Error getting platform suggestions: {str(e)}")
return {}
def validate_platform_content(
self,
content: Dict[str, Any],
platform: str
) -> Dict[str, Any]:
"""Validate content for a specific platform."""
try:
validation_result = self.platform_adapter.validate_content(
content=content,
platform=platform
)
return {
'platform': platform,
'is_valid': validation_result,
'specifications': self.platform_adapter.get_platform_specs(platform)
}
except Exception as e:
logger.error(f"Error validating platform content: {str(e)}")
return {
'platform': platform,
'is_valid': False,
'error': str(e)
}
def optimize_cross_platform_content(
self,
content: Dict[str, Any],
platforms: List[str]
) -> Dict[str, Any]:
"""Optimize content for multiple platforms."""
try:
optimized_content = {}
for platform in platforms:
platform_optimized = self.platform_adapter.optimize_content(
content=content,
platform=platform
)
if platform_optimized:
optimized_content[platform] = platform_optimized
return optimized_content
except Exception as e:
logger.error(f"Error optimizing cross-platform content: {str(e)}")
return {}

View File

@@ -0,0 +1,137 @@
"""
Platform adapters for content calendar.
"""
import streamlit as st
from typing import Dict, Any, List, Optional
from loguru import logger
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer
from lib.ai_seo_tools.content_gap_analysis.main import ContentGapAnalysis
from lib.ai_seo_tools.content_title_generator import ai_title_generator
from lib.ai_seo_tools.meta_desc_generator import metadesc_generator_main
from lib.ai_seo_tools.seo_structured_data import ai_structured_data
import asyncio
import sys
import os
import json
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/platform_adapters.log",
rotation="50 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Ensure logs directory exists
os.makedirs("logs", exist_ok=True)
class UnifiedPlatformAdapter:
"""Unified adapter for different social media platforms."""
def __init__(self):
"""Initialize the platform adapter."""
self.platform_handlers = {
'instagram': self._handle_instagram,
'linkedin': self._handle_linkedin,
'twitter': self._handle_twitter,
'facebook': self._handle_facebook
}
logger.info("UnifiedPlatformAdapter initialized")
def generate_content(self, platform: str, data: Dict[str, Any]) -> Dict[str, Any]:
"""
Generate content for a specific platform.
Args:
platform: Target platform
data: Content data
Returns:
Dictionary containing generated content
"""
try:
handler = self.platform_handlers.get(platform.lower())
if not handler:
raise ValueError(f"Unsupported platform: {platform}")
return handler(data)
except Exception as e:
error_msg = f"Error generating content for {platform}: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
'error': error_msg,
'content': None
}
def _handle_instagram(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Handle Instagram content generation."""
try:
# Use content title generator for Instagram captions
caption = ai_title_generator(data)
return {
'platform': 'instagram',
'content': caption
}
except Exception as e:
logger.error(f"Error generating Instagram content: {str(e)}")
return {
'platform': 'instagram',
'error': str(e)
}
def _handle_linkedin(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Handle LinkedIn content generation."""
try:
# Use meta description generator for LinkedIn posts
post = metadesc_generator_main(data)
return {
'platform': 'linkedin',
'content': post
}
except Exception as e:
logger.error(f"Error generating LinkedIn content: {str(e)}")
return {
'platform': 'linkedin',
'error': str(e)
}
def _handle_twitter(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Handle Twitter content generation."""
try:
# Use content title generator for tweets
tweet = ai_title_generator(data)
return {
'platform': 'twitter',
'content': tweet
}
except Exception as e:
logger.error(f"Error generating Twitter content: {str(e)}")
return {
'platform': 'twitter',
'error': str(e)
}
def _handle_facebook(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Handle Facebook content generation."""
try:
# Use meta description generator for Facebook posts
post = metadesc_generator_main(data)
return {
'platform': 'facebook',
'content': post
}
except Exception as e:
logger.error(f"Error generating Facebook content: {str(e)}")
return {
'platform': 'facebook',
'error': str(e)
}

View File

@@ -0,0 +1,219 @@
import logging
from typing import Dict, Any, List, Optional
from datetime import datetime
from ...meta_desc_generator import generate_blog_metadesc
from ...content_title_generator import generate_blog_titles
from ...seo_structured_data import generate_json_data
logger = logging.getLogger(__name__)
class SEOOptimizer:
"""Integrates SEO tools with content calendar system."""
def __init__(self):
"""Initialize the SEO optimizer."""
self._setup_logging()
def _setup_logging(self):
"""Configure logging for SEO optimizer."""
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
def optimize_content(
self,
content: Dict[str, Any],
content_type: str = 'article',
language: str = 'English',
search_intent: str = 'Informational Intent'
) -> Dict[str, Any]:
"""
Optimize content for SEO using existing tools.
Args:
content: Content to optimize
content_type: Type of content (article, product, etc.)
language: Content language
search_intent: Search intent type
Returns:
Optimized content with SEO elements
"""
try:
# Extract content details
title = content.get('title', '')
keywords = content.get('keywords', [])
content_text = content.get('content', '')
# Generate SEO elements
optimized_title = self._optimize_title(
title=title,
keywords=keywords,
content_type=content_type,
language=language,
search_intent=search_intent
)
meta_description = self._generate_meta_description(
keywords=keywords,
content_type=content_type,
language=language,
search_intent=search_intent
)
structured_data = self._generate_structured_data(
content=content,
content_type=content_type
)
return {
'original_content': content,
'seo_optimized': {
'title': optimized_title,
'meta_description': meta_description,
'structured_data': structured_data,
'keywords': keywords,
'content_type': content_type,
'language': language,
'search_intent': search_intent
}
}
except Exception as e:
logger.error(f"Error optimizing content: {str(e)}")
return {
'error': str(e)
}
def _optimize_title(
self,
title: str,
keywords: List[str],
content_type: str,
language: str,
search_intent: str
) -> List[str]:
"""Generate SEO-optimized titles."""
try:
# Convert keywords list to comma-separated string
keywords_str = ', '.join(keywords)
# Generate titles using existing tool
titles = generate_blog_titles(
input_blog_keywords=keywords_str,
input_blog_content=title,
input_title_type=content_type,
input_title_intent=search_intent,
input_language=language
)
return titles.split('\n') if titles else []
except Exception as e:
logger.error(f"Error optimizing title: {str(e)}")
return []
def _generate_meta_description(
self,
keywords: List[str],
content_type: str,
language: str,
search_intent: str
) -> List[str]:
"""Generate SEO-optimized meta descriptions."""
try:
# Convert keywords list to comma-separated string
keywords_str = ', '.join(keywords)
# Generate meta descriptions using existing tool
descriptions = generate_blog_metadesc(
keywords=keywords_str,
tone='Informative',
search_type=search_intent,
language=language
)
return descriptions.split('\n') if descriptions else []
except Exception as e:
logger.error(f"Error generating meta description: {str(e)}")
return []
def _generate_structured_data(
self,
content: Dict[str, Any],
content_type: str
) -> Optional[Dict[str, Any]]:
"""Generate structured data for content."""
try:
# Prepare content details for structured data
details = {
'Headline': content.get('title', ''),
'Author': content.get('author', ''),
'Date Published': content.get('publish_date', datetime.now().isoformat()),
'Keywords': ', '.join(content.get('keywords', [])),
'Description': content.get('description', ''),
'Image URL': content.get('image_url', '')
}
# Generate structured data using existing tool
structured_data = generate_json_data(
content_type=content_type,
details=details,
url=content.get('url', '')
)
return structured_data
except Exception as e:
logger.error(f"Error generating structured data: {str(e)}")
return None
def optimize_calendar_content(
self,
calendar: Dict[str, Any],
content_type: str = 'article',
language: str = 'English',
search_intent: str = 'Informational Intent'
) -> Dict[str, Any]:
"""
Optimize all content in calendar for SEO.
Args:
calendar: Content calendar to optimize
content_type: Type of content
language: Content language
search_intent: Search intent type
Returns:
Calendar with SEO-optimized content
"""
try:
optimized_calendar = {
'metadata': calendar.get('metadata', {}),
'content_items': []
}
# Optimize each content item
for item in calendar.get('content_items', []):
optimized_item = self.optimize_content(
content=item,
content_type=content_type,
language=language,
search_intent=search_intent
)
if optimized_item:
optimized_calendar['content_items'].append(optimized_item)
return optimized_calendar
except Exception as e:
logger.error(f"Error optimizing calendar content: {str(e)}")
return {
'error': str(e)
}

View File

@@ -0,0 +1,143 @@
"""SEO tools integration for content calendar."""
import streamlit as st
from loguru import logger
from typing import Dict, Any, List, Optional
import asyncio
import sys
import os
from lib.ai_seo_tools.content_title_generator import ai_title_generator
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/seo_tools_integration.log",
rotation="50 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Ensure logs directory exists
os.makedirs("logs", exist_ok=True)
class SEOToolsIntegration:
"""Integration with SEO tools for content calendar."""
def __init__(self):
"""Initialize the SEO tools integration."""
self.website_analyzer = WebsiteAnalyzer()
logger.info("SEOToolsIntegration initialized")
def analyze_content(self, url: str) -> Dict[str, Any]:
"""
Analyze content for SEO optimization.
Args:
url: The URL to analyze
Returns:
Dictionary containing SEO analysis results
"""
try:
# Analyze website
analysis = self.website_analyzer.analyze_website(url)
if not analysis.get('success', False):
return {
'error': analysis.get('error', 'Unknown error in analysis'),
'seo_score': 0,
'recommendations': []
}
# Extract SEO information
seo_info = analysis['data']['analysis']['seo_info']
return {
'seo_score': seo_info.get('overall_score', 0),
'meta_tags': seo_info.get('meta_tags', {}),
'content': seo_info.get('content', {}),
'recommendations': seo_info.get('recommendations', [])
}
except Exception as e:
error_msg = f"Error analyzing content: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
'error': error_msg,
'seo_score': 0,
'recommendations': []
}
def generate_title(self, url: str) -> Dict[str, Any]:
"""
Generate SEO-optimized title.
Args:
url: The URL to analyze
Returns:
Dictionary containing title suggestions
"""
return ai_title_generator(url)
def optimize_content(self, content: str, keywords: List[str]) -> Dict[str, Any]:
"""
Optimize content for SEO.
Args:
content: The content to optimize
keywords: List of target keywords
Returns:
Dictionary containing optimization suggestions
"""
try:
# Prepare prompt for content optimization
prompt = f"""Optimize the following content for SEO:
Content: {content}
Target Keywords: {', '.join(keywords)}
Provide optimization suggestions for:
1. Keyword usage and placement
2. Content structure and readability
3. Meta information
4. Internal linking opportunities
5. Content length and depth
Format the response as JSON with 'suggestions' and 'score' keys."""
# Get AI optimization suggestions
suggestions = llm_text_gen(
prompt=prompt,
system_prompt="You are an SEO expert specializing in content optimization.",
response_format="json_object"
)
if not suggestions:
return {
'error': 'Failed to generate optimization suggestions',
'suggestions': [],
'score': 0
}
return {
'suggestions': suggestions.get('suggestions', []),
'score': suggestions.get('score', 0)
}
except Exception as e:
error_msg = f"Error optimizing content: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
'error': error_msg,
'suggestions': [],
'score': 0
}

View File

@@ -0,0 +1,237 @@
import logging
import sys
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler('content_calendar_debug.log', mode='a')
]
)
logger = logging.getLogger(__name__)
from datetime import datetime
from typing import Dict, List, Any, Optional
from dataclasses import dataclass, field
from enum import Enum
import pandas as pd
class ContentType(Enum):
"""Types of content that can be scheduled."""
BLOG_POST = "blog_post"
SOCIAL_MEDIA = "social_media"
VIDEO = "video"
PODCAST = "podcast"
NEWSLETTER = "newsletter"
LANDING_PAGE = "landing_page"
class Platform(Enum):
"""Supported content platforms."""
WEBSITE = "website"
FACEBOOK = "facebook"
TWITTER = "twitter"
LINKEDIN = "linkedin"
INSTAGRAM = "instagram"
YOUTUBE = "youtube"
MEDIUM = "medium"
@dataclass
class SEOData:
"""SEO-related data for content."""
title: str
meta_description: str
keywords: List[str]
structured_data: Dict[str, Any]
canonical_url: Optional[str] = None
og_tags: Optional[Dict[str, str]] = None
twitter_cards: Optional[Dict[str, str]] = None
@staticmethod
def from_dict(data):
return SEOData(
title=data.get('title', ''),
meta_description=data.get('meta_description', ''),
keywords=data.get('keywords', []),
structured_data=data.get('structured_data', {}),
canonical_url=data.get('canonical_url'),
og_tags=data.get('og_tags'),
twitter_cards=data.get('twitter_cards')
)
@dataclass
class ContentItem:
"""Represents a single content item in the calendar."""
title: str
description: str
content_type: ContentType
platforms: List[Platform]
publish_date: datetime
seo_data: SEOData
status: str = "draft"
author: Optional[str] = None
tags: List[str] = field(default_factory=list)
notes: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
"""Convert content item to dictionary."""
return {
'title': self.title,
'description': self.description,
'content_type': self.content_type.value,
'platforms': [p.value for p in self.platforms],
'publish_date': self.publish_date.isoformat(),
'seo_data': {
'title': self.seo_data.title,
'meta_description': self.seo_data.meta_description,
'keywords': self.seo_data.keywords,
'structured_data': self.seo_data.structured_data,
'canonical_url': self.seo_data.canonical_url,
'og_tags': self.seo_data.og_tags,
'twitter_cards': self.seo_data.twitter_cards
},
'status': self.status,
'author': self.author,
'tags': self.tags,
'notes': self.notes
}
@staticmethod
def from_dict(data):
from .calendar import ContentType, Platform, SEOData
return ContentItem(
title=data['title'],
description=data.get('description', ''),
content_type=ContentType(data['content_type']),
platforms=[Platform(p) for p in data['platforms']],
publish_date=pd.to_datetime(data['publish_date']),
seo_data=SEOData.from_dict(data.get('seo_data', {})),
status=data.get('status', 'draft'),
author=data.get('author'),
tags=data.get('tags', []),
notes=data.get('notes')
)
@dataclass
class Calendar:
"""Represents a content calendar."""
start_date: datetime
duration: str # 'weekly', 'monthly', 'quarterly'
platforms: List[Platform]
schedule: Dict[str, List[ContentItem]]
name: Optional[str] = None
description: Optional[str] = None
def __init__(self, start_date: datetime, duration: str, platforms: List[Platform],
schedule: Dict[str, List[ContentItem]], name: Optional[str] = None,
description: Optional[str] = None):
"""Initialize a new calendar.
Args:
start_date: Start date of the calendar
duration: Duration of the calendar ('weekly', 'monthly', 'quarterly')
platforms: List of platforms to schedule content for
schedule: Dictionary mapping dates to content items
name: Optional name for the calendar
description: Optional description of the calendar
"""
self.start_date = start_date
self.duration = duration
self.platforms = platforms
self.schedule = schedule
self.name = name
self.description = description
self.content_items: List[ContentItem] = []
self.logger = logging.getLogger('content_calendar.calendar')
# Initialize content_items from schedule
for items in self.schedule.values():
self.content_items.extend(items)
def get_all_content(self) -> List[ContentItem]:
"""Get all content items in the calendar.
Returns:
List of all ContentItem objects in the calendar
"""
try:
self.logger.debug(f"Getting all content items. Count: {len(self.content_items)}")
return self.content_items
except Exception as e:
self.logger.error(f"Error getting all content: {str(e)}")
return []
def to_dict(self) -> Dict[str, Any]:
"""Convert calendar to dictionary."""
return {
'name': self.name,
'description': self.description,
'start_date': self.start_date.isoformat(),
'duration': self.duration,
'platforms': [p.value for p in self.platforms],
'schedule': {
date: [item.to_dict() for item in items]
for date, items in self.schedule.items()
}
}
def export(self, format: str = 'json') -> Dict[str, Any]:
"""
Export calendar in specified format.
Currently only supports JSON format.
"""
if format.lower() != 'json':
raise ValueError(f"Unsupported export format: {format}")
return self.to_dict()
def get_content_for_date(self, date: datetime) -> List[ContentItem]:
"""Get all content items scheduled for a specific date."""
date_str = date.strftime('%Y-%m-%d')
return self.schedule.get(date_str, [])
def get_content_for_platform(
self,
platform: Platform
) -> List[ContentItem]:
"""Get all content items for a specific platform."""
all_content = []
for items in self.schedule.values():
platform_content = [
item for item in items
if platform in item.platforms
]
all_content.extend(platform_content)
return all_content
def add_content(self, content: ContentItem) -> None:
"""Add a new content item to the calendar."""
date_str = content.publish_date.strftime('%Y-%m-%d')
if date_str not in self.schedule:
self.schedule[date_str] = []
self.schedule[date_str].append(content)
def remove_content(self, content: ContentItem) -> None:
"""Remove a content item from the calendar."""
date_str = content.publish_date.strftime('%Y-%m-%d')
if date_str in self.schedule:
self.schedule[date_str] = [
item for item in self.schedule[date_str]
if item != content
]
@staticmethod
def from_dict(data):
from .calendar import ContentItem, Platform
schedule = {
date: [ContentItem.from_dict(item) for item in items]
for date, items in data.get('schedule', {}).items()
}
return Calendar(
start_date=pd.to_datetime(data['start_date']),
duration=data['duration'],
platforms=[Platform(p) for p in data['platforms']],
schedule=schedule,
name=data.get('name'),
description=data.get('description')
)

View File

@@ -0,0 +1,185 @@
import unittest
from typing import Dict, Any
from ..models.calendar import ContentType
from ..core.ai_generator import AIContentGenerator
class TestAIContentGenerator(unittest.TestCase):
"""Test cases for AIContentGenerator."""
def setUp(self):
"""Set up test cases."""
self.generator = AIContentGenerator()
self.test_title = "10 Ways to Improve Your SEO Strategy"
self.test_content_type = ContentType.BLOG_POST
self.test_context = {
"website_url": "https://example.com",
"target_audience": "digital marketers",
"content_goals": ["educate", "generate leads"]
}
def test_generate_headings(self):
"""Test heading generation."""
headings = self.generator.generate_headings(
title=self.test_title,
content_type=self.test_content_type,
context=self.test_context
)
self.assertIsInstance(headings, list)
for heading in headings:
self.assertIn('title', heading)
self.assertIn('level', heading)
self.assertIn('keywords', heading)
self.assertIn('summary', heading)
# Verify heading level
self.assertEqual(heading['level'], 1)
# Verify heading content
self.assertIsInstance(heading['title'], str)
self.assertIsInstance(heading['keywords'], list)
self.assertIsInstance(heading['summary'], str)
def test_generate_subheadings(self):
"""Test subheading generation."""
main_heading = {
'title': 'Understanding SEO Basics',
'level': 1,
'keywords': ['SEO', 'basics', 'fundamentals'],
'summary': 'Introduction to core SEO concepts'
}
subheadings = self.generator.generate_subheadings(
main_heading=main_heading,
content_type=self.test_content_type,
context=self.test_context
)
self.assertIsInstance(subheadings, list)
for subheading in subheadings:
self.assertIn('title', subheading)
self.assertIn('level', subheading)
self.assertIn('keywords', subheading)
self.assertIn('summary', subheading)
# Verify subheading level
self.assertEqual(subheading['level'], 2)
# Verify subheading content
self.assertIsInstance(subheading['title'], str)
self.assertIsInstance(subheading['keywords'], list)
self.assertIsInstance(subheading['summary'], str)
def test_generate_key_points(self):
"""Test key points generation."""
key_points = self.generator.generate_key_points(
title=self.test_title,
content_type=self.test_content_type,
context=self.test_context
)
self.assertIsInstance(key_points, list)
for point in key_points:
self.assertIn('point', point)
self.assertIn('importance', point)
self.assertIn('supporting_evidence', point)
self.assertIn('related_keywords', point)
# Verify point content
self.assertIsInstance(point['point'], str)
self.assertIn(point['importance'], ['high', 'medium', 'low'])
self.assertIsInstance(point['supporting_evidence'], list)
self.assertIsInstance(point['related_keywords'], list)
def test_generate_content_flow(self):
"""Test content flow generation."""
outline = {
'main_headings': [
{
'title': 'Introduction',
'level': 1,
'keywords': ['SEO', 'introduction'],
'summary': 'Overview of SEO importance'
}
],
'subheadings': {
'Introduction': [
{
'title': 'What is SEO?',
'level': 2,
'keywords': ['definition', 'basics'],
'summary': 'Basic definition of SEO'
}
]
}
}
flow = self.generator.generate_content_flow(
title=self.test_title,
content_type=self.test_content_type,
outline=outline
)
self.assertIsInstance(flow, dict)
self.assertIn('introduction', flow)
self.assertIn('main_sections', flow)
self.assertIn('conclusion', flow)
self.assertIn('transitions', flow)
self.assertIn('content_pacing', flow)
# Verify flow content
self.assertIsInstance(flow['introduction'], dict)
self.assertIsInstance(flow['main_sections'], list)
self.assertIsInstance(flow['conclusion'], dict)
self.assertIsInstance(flow['transitions'], list)
self.assertIsInstance(flow['content_pacing'], dict)
def test_prompt_creation(self):
"""Test prompt creation methods."""
# Test heading prompt
heading_prompt = self.generator._create_heading_prompt(
title=self.test_title,
content_type=self.test_content_type,
gaps={'opportunities': ['keyword research', 'content optimization']}
)
self.assertIsInstance(heading_prompt, str)
self.assertIn(self.test_title, heading_prompt)
self.assertIn(self.test_content_type.value, heading_prompt)
# Test subheading prompt
main_heading = {
'title': 'Understanding SEO Basics',
'level': 1,
'keywords': ['SEO', 'basics'],
'summary': 'Introduction to SEO'
}
subheading_prompt = self.generator._create_subheading_prompt(
main_heading=main_heading,
content_type=self.test_content_type,
context=self.test_context
)
self.assertIsInstance(subheading_prompt, str)
self.assertIn(main_heading['title'], subheading_prompt)
# Test key points prompt
key_points_prompt = self.generator._create_key_points_prompt(
title=self.test_title,
content_type=self.test_content_type,
seo_data={'keywords': ['SEO', 'strategy']},
context=self.test_context
)
self.assertIsInstance(key_points_prompt, str)
self.assertIn(self.test_title, key_points_prompt)
# Test flow prompt
flow_prompt = self.generator._create_flow_prompt(
title=self.test_title,
content_type=self.test_content_type,
outline={'main_headings': []}
)
self.assertIsInstance(flow_prompt, str)
self.assertIn(self.test_title, flow_prompt)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,132 @@
import unittest
from datetime import datetime
from typing import Dict, Any
from ..models.calendar import ContentItem, ContentType, Platform, SEOData
from ..core.content_brief import ContentBriefGenerator
class TestContentBriefGenerator(unittest.TestCase):
"""Test cases for ContentBriefGenerator."""
def setUp(self):
"""Set up test cases."""
self.generator = ContentBriefGenerator()
self.test_content_item = self._create_test_content_item()
def _create_test_content_item(self) -> ContentItem:
"""Create a test content item."""
return ContentItem(
id="test-001",
title="10 Ways to Improve Your SEO Strategy",
description="A comprehensive guide to enhancing your website's SEO performance",
content_type=ContentType.BLOG_POST,
platforms=[Platform.WEBSITE, Platform.LINKEDIN],
publish_date=datetime.now(),
seo_data=SEOData(
keywords=["SEO", "search engine optimization", "digital marketing"],
meta_description="Learn effective SEO strategies to boost your website's visibility",
structured_data={}
),
platform_specs={
"website": {
"format": "blog post",
"min_length": 1500
},
"linkedin": {
"format": "article",
"min_length": 800
}
},
context={
"website_url": "https://example.com",
"target_audience": "digital marketers",
"content_goals": ["educate", "generate leads"]
}
)
def test_generate_brief(self):
"""Test content brief generation."""
# Generate brief
brief = self.generator.generate_brief(
content_item=self.test_content_item,
target_audience={
"demographics": {
"age_range": "25-45",
"profession": "digital marketers"
},
"interests": ["SEO", "content marketing", "digital strategy"],
"pain_points": [
"low search rankings",
"poor content performance",
"lack of organic traffic"
]
}
)
# Verify brief structure
self.assertIsInstance(brief, dict)
self.assertIn('title', brief)
self.assertIn('content_type', brief)
self.assertIn('outline', brief)
self.assertIn('key_points', brief)
self.assertIn('content_flow', brief)
self.assertIn('target_audience', brief)
self.assertIn('seo_data', brief)
self.assertIn('platform_specs', brief)
# Verify outline structure
outline = brief['outline']
self.assertIn('main_headings', outline)
self.assertIn('subheadings', outline)
# Verify key points
self.assertIsInstance(brief['key_points'], list)
# Verify content flow
flow = brief['content_flow']
self.assertIn('introduction', flow)
self.assertIn('main_sections', flow)
self.assertIn('conclusion', flow)
self.assertIn('transitions', flow)
self.assertIn('content_pacing', flow)
def test_generate_brief_without_audience(self):
"""Test content brief generation without target audience data."""
brief = self.generator.generate_brief(
content_item=self.test_content_item
)
self.assertIsInstance(brief, dict)
self.assertIn('target_audience', brief)
self.assertEqual(brief['target_audience'], {})
def test_generate_outline(self):
"""Test outline generation."""
outline = self.generator._generate_outline(self.test_content_item)
self.assertIsInstance(outline, dict)
self.assertIn('main_headings', outline)
self.assertIn('subheadings', outline)
# Verify main headings
main_headings = outline['main_headings']
self.assertIsInstance(main_headings, list)
for heading in main_headings:
self.assertIn('title', heading)
self.assertIn('level', heading)
self.assertIn('keywords', heading)
self.assertIn('summary', heading)
# Verify subheadings
subheadings = outline['subheadings']
self.assertIsInstance(subheadings, dict)
for heading_title, heading_subheadings in subheadings.items():
self.assertIsInstance(heading_subheadings, list)
for subheading in heading_subheadings:
self.assertIn('title', subheading)
self.assertIn('level', subheading)
self.assertIn('keywords', subheading)
self.assertIn('summary', subheading)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,171 @@
import unittest
from datetime import datetime, timedelta
from typing import Dict, Any
from ..integrations.integration_manager import IntegrationManager
class TestIntegrationManager(unittest.TestCase):
"""Test cases for the IntegrationManager class."""
def setUp(self):
"""Set up test fixtures."""
self.integration_manager = IntegrationManager()
self.start_date = datetime.now()
self.end_date = self.start_date + timedelta(days=30)
self.platforms = ['instagram', 'twitter', 'linkedin', 'blog', 'facebook']
self.content_types = ['article', 'social', 'video']
self.target_audience = {
'age_range': '25-34',
'interests': ['technology', 'marketing'],
'location': 'global'
}
self.industry = 'technology'
self.keywords = ['AI', 'content marketing', 'social media']
# Sample content item
self.sample_content = {
'title': 'The Future of AI in Content Marketing',
'content': 'AI is revolutionizing content marketing...',
'content_type': 'article',
'keywords': ['AI', 'content marketing', 'automation'],
'target_audience': self.target_audience,
'industry': self.industry
}
def test_create_cross_platform_calendar(self):
"""Test creating a cross-platform content calendar."""
calendar = self.integration_manager.create_cross_platform_calendar(
start_date=self.start_date,
end_date=self.end_date,
platforms=self.platforms,
content_types=self.content_types,
target_audience=self.target_audience,
industry=self.industry,
keywords=self.keywords
)
# Check basic structure
self.assertIn('base_calendar', calendar)
self.assertIn('platform_calendars', calendar)
self.assertIn('metadata', calendar)
# Check platform calendars
platform_calendars = calendar['platform_calendars']
self.assertEqual(len(platform_calendars), len(self.platforms))
for platform in self.platforms:
self.assertIn(platform, platform_calendars)
platform_calendar = platform_calendars[platform]
self.assertIn('content_items', platform_calendar)
self.assertIn('metadata', platform_calendar)
def test_adapt_calendar_for_platform(self):
"""Test adapting calendar for a specific platform."""
# Create base calendar
calendar = self.integration_manager.create_cross_platform_calendar(
start_date=self.start_date,
end_date=self.end_date,
platforms=[self.platforms[0]], # Test with just Instagram
content_types=self.content_types,
target_audience=self.target_audience,
industry=self.industry,
keywords=self.keywords
)
# Get platform calendar
platform_calendar = calendar['platform_calendars'][self.platforms[0]]
# Check structure
self.assertIn('content_items', platform_calendar)
self.assertIn('metadata', platform_calendar)
# Check content items
for item in platform_calendar['content_items']:
self.assertIn('original_item', item)
self.assertIn('adapted_content', item)
self.assertIn('platform_specifics', item)
def test_adapt_content_item(self):
"""Test adapting a content item for a platform."""
adapted_item = self.integration_manager._adapt_content_item(
item=self.sample_content,
platform='instagram'
)
# Check structure
self.assertIsNotNone(adapted_item)
self.assertIn('original_item', adapted_item)
self.assertIn('adapted_content', adapted_item)
self.assertIn('platform_specifics', adapted_item)
# Check content adaptation
adapted_content = adapted_item['adapted_content']
self.assertIn('captions', adapted_content)
self.assertIn('hashtags', adapted_content)
self.assertIn('media_suggestions', adapted_content)
def test_get_platform_suggestions(self):
"""Test getting platform-specific suggestions."""
suggestions = self.integration_manager.get_platform_suggestions(
content=self.sample_content,
platforms=self.platforms
)
# Check structure
self.assertEqual(len(suggestions), len(self.platforms))
for platform in self.platforms:
self.assertIn(platform, suggestions)
platform_suggestions = suggestions[platform]
self.assertIsInstance(platform_suggestions, dict)
def test_validate_platform_content(self):
"""Test validating content for a platform."""
validation = self.integration_manager.validate_platform_content(
content=self.sample_content,
platform='instagram'
)
# Check structure
self.assertIn('platform', validation)
self.assertIn('is_valid', validation)
self.assertIn('specifications', validation)
# Check validation result
self.assertIsInstance(validation['is_valid'], bool)
def test_optimize_cross_platform_content(self):
"""Test optimizing content for multiple platforms."""
optimized = self.integration_manager.optimize_cross_platform_content(
content=self.sample_content,
platforms=self.platforms
)
# Check structure
self.assertEqual(len(optimized), len(self.platforms))
for platform in self.platforms:
self.assertIn(platform, optimized)
platform_optimized = optimized[platform]
self.assertIsInstance(platform_optimized, dict)
def test_error_handling(self):
"""Test error handling with invalid inputs."""
# Test with invalid platform
with self.assertRaises(Exception):
self.integration_manager.validate_platform_content(
content=self.sample_content,
platform='invalid_platform'
)
# Test with invalid content
invalid_content = {'title': 'Invalid Content'}
validation = self.integration_manager.validate_platform_content(
content=invalid_content,
platform='instagram'
)
self.assertFalse(validation['is_valid'])
self.assertIn('error', validation)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,186 @@
import unittest
from typing import Dict, Any
from datetime import datetime
from ..integrations.platform_adapters import UnifiedPlatformAdapter
class TestUnifiedPlatformAdapter(unittest.TestCase):
"""Test cases for the UnifiedPlatformAdapter."""
def setUp(self):
"""Set up test cases."""
self.adapter = UnifiedPlatformAdapter()
self.test_content = {
'title': 'Test Content',
'content': 'This is a test content for platform adaptation.',
'keywords': ['test', 'content', 'platform'],
'tone': 'professional',
'cta': 'Learn More',
'audience': 'For All',
'language': 'English',
'industry': 'technology',
'word_count': 1000
}
def test_adapt_instagram_content(self):
"""Test Instagram content adaptation."""
adapted_content = self.adapter.adapt_content(
content=self.test_content,
platform='instagram'
)
self.assertIsInstance(adapted_content, dict)
self.assertIn('captions', adapted_content)
self.assertIn('hashtags', adapted_content)
self.assertIn('media_suggestions', adapted_content)
self.assertIn('platform_specific', adapted_content)
def test_adapt_twitter_content(self):
"""Test Twitter content adaptation."""
adapted_content = self.adapter.adapt_content(
content=self.test_content,
platform='twitter'
)
self.assertIsInstance(adapted_content, dict)
self.assertIn('tweets', adapted_content)
self.assertIn('thread_structure', adapted_content)
self.assertIn('media_suggestions', adapted_content)
self.assertIn('platform_specific', adapted_content)
def test_adapt_linkedin_content(self):
"""Test LinkedIn content adaptation."""
adapted_content = self.adapter.adapt_content(
content=self.test_content,
platform='linkedin'
)
self.assertIsInstance(adapted_content, dict)
self.assertIn('post', adapted_content)
self.assertIn('engagement_optimization', adapted_content)
self.assertIn('media_suggestions', adapted_content)
self.assertIn('platform_specific', adapted_content)
def test_adapt_blog_content(self):
"""Test blog content adaptation."""
adapted_content = self.adapter.adapt_content(
content=self.test_content,
platform='blog'
)
self.assertIsInstance(adapted_content, dict)
self.assertIn('post', adapted_content)
self.assertIn('seo_optimization', adapted_content)
self.assertIn('media_suggestions', adapted_content)
self.assertIn('platform_specific', adapted_content)
def test_adapt_facebook_content(self):
"""Test Facebook content adaptation."""
adapted_content = self.adapter.adapt_content(
content=self.test_content,
platform='facebook'
)
self.assertIsInstance(adapted_content, dict)
self.assertIn('post', adapted_content)
self.assertIn('engagement_optimization', adapted_content)
self.assertIn('media_suggestions', adapted_content)
self.assertIn('platform_specific', adapted_content)
def test_validate_content(self):
"""Test content validation."""
# Test valid content
self.assertTrue(
self.adapter.validate_content(
self.test_content,
'instagram'
)
)
# Test invalid content (missing required fields)
invalid_content = {
'title': 'Test Content',
'content': 'This is a test content.'
}
self.assertFalse(
self.adapter.validate_content(
invalid_content,
'instagram'
)
)
def test_unsupported_platform(self):
"""Test handling of unsupported platform."""
with self.assertRaises(ValueError):
self.adapter.adapt_content(
content=self.test_content,
platform='unsupported_platform'
)
def test_content_adaptation_with_context(self):
"""Test content adaptation with additional context."""
context = {
'target_audience': 'professionals',
'campaign_goals': ['awareness', 'engagement'],
'brand_voice': 'authoritative'
}
adapted_content = self.adapter.adapt_content(
content=self.test_content,
platform='linkedin',
context=context
)
self.assertIsInstance(adapted_content, dict)
self.assertIn('post', adapted_content)
self.assertIn('engagement_optimization', adapted_content)
def test_error_handling(self):
"""Test error handling in content adaptation."""
# Test with invalid content structure
invalid_content = {
'title': 123, # Invalid type
'content': None # Missing required field
}
adapted_content = self.adapter.adapt_content(
content=invalid_content,
platform='blog'
)
self.assertIn('error', adapted_content)
def test_platform_specs(self):
"""Test platform specifications."""
specs = self.adapter.platform_specs
# Check Instagram specs
self.assertIn('instagram', specs)
self.assertIn('max_caption_length', specs['instagram'])
self.assertIn('max_hashtags', specs['instagram'])
self.assertIn('required_fields', specs['instagram'])
# Check Twitter specs
self.assertIn('twitter', specs)
self.assertIn('max_tweet_length', specs['twitter'])
self.assertIn('max_thread_length', specs['twitter'])
self.assertIn('required_fields', specs['twitter'])
# Check LinkedIn specs
self.assertIn('linkedin', specs)
self.assertIn('max_post_length', specs['linkedin'])
self.assertIn('required_fields', specs['linkedin'])
# Check blog specs
self.assertIn('blog', specs)
self.assertIn('min_word_count', specs['blog'])
self.assertIn('max_word_count', specs['blog'])
self.assertIn('required_fields', specs['blog'])
# Check Facebook specs
self.assertIn('facebook', specs)
self.assertIn('max_post_length', specs['facebook'])
self.assertIn('required_fields', specs['facebook'])
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,132 @@
import unittest
from datetime import datetime
from typing import Dict, Any
from ..integrations.seo_optimizer import SEOOptimizer
class TestSEOOptimizer(unittest.TestCase):
"""Test cases for the SEOOptimizer class."""
def setUp(self):
"""Set up test fixtures."""
self.seo_optimizer = SEOOptimizer()
# Sample content for testing
self.sample_content = {
'title': 'The Future of AI in Content Marketing',
'content': 'AI is revolutionizing content marketing...',
'keywords': ['AI', 'content marketing', 'automation'],
'author': 'John Doe',
'publish_date': datetime.now().isoformat(),
'description': 'An in-depth look at AI in content marketing',
'image_url': 'https://example.com/image.jpg',
'url': 'https://example.com/article'
}
# Sample calendar for testing
self.sample_calendar = {
'metadata': {
'start_date': datetime.now().isoformat(),
'end_date': datetime.now().isoformat(),
'platforms': ['blog', 'social'],
'content_types': ['article']
},
'content_items': [self.sample_content]
}
def test_optimize_content(self):
"""Test content optimization."""
optimized = self.seo_optimizer.optimize_content(
content=self.sample_content,
content_type='article',
language='English',
search_intent='Informational Intent'
)
# Check structure
self.assertIn('original_content', optimized)
self.assertIn('seo_optimized', optimized)
# Check SEO elements
seo_elements = optimized['seo_optimized']
self.assertIn('title', seo_elements)
self.assertIn('meta_description', seo_elements)
self.assertIn('structured_data', seo_elements)
self.assertIn('keywords', seo_elements)
def test_optimize_title(self):
"""Test title optimization."""
titles = self.seo_optimizer._optimize_title(
title=self.sample_content['title'],
keywords=self.sample_content['keywords'],
content_type='article',
language='English',
search_intent='Informational Intent'
)
# Check titles
self.assertIsInstance(titles, list)
self.assertTrue(len(titles) > 0)
def test_generate_meta_description(self):
"""Test meta description generation."""
descriptions = self.seo_optimizer._generate_meta_description(
keywords=self.sample_content['keywords'],
content_type='article',
language='English',
search_intent='Informational Intent'
)
# Check descriptions
self.assertIsInstance(descriptions, list)
self.assertTrue(len(descriptions) > 0)
def test_generate_structured_data(self):
"""Test structured data generation."""
structured_data = self.seo_optimizer._generate_structured_data(
content=self.sample_content,
content_type='article'
)
# Check structured data
self.assertIsNotNone(structured_data)
def test_optimize_calendar_content(self):
"""Test calendar content optimization."""
optimized_calendar = self.seo_optimizer.optimize_calendar_content(
calendar=self.sample_calendar,
content_type='article',
language='English',
search_intent='Informational Intent'
)
# Check structure
self.assertIn('metadata', optimized_calendar)
self.assertIn('content_items', optimized_calendar)
# Check content items
self.assertTrue(len(optimized_calendar['content_items']) > 0)
for item in optimized_calendar['content_items']:
self.assertIn('original_content', item)
self.assertIn('seo_optimized', item)
def test_error_handling(self):
"""Test error handling with invalid inputs."""
# Test with invalid content
invalid_content = {'title': 'Invalid Content'}
optimized = self.seo_optimizer.optimize_content(
content=invalid_content,
content_type='article'
)
self.assertIn('error', optimized)
# Test with invalid calendar
invalid_calendar = {'metadata': {}}
optimized_calendar = self.seo_optimizer.optimize_calendar_content(
calendar=invalid_calendar,
content_type='article'
)
self.assertIn('error', optimized_calendar)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,21 @@
import streamlit as st
def render_add_content_modal(selected_date, on_add_content, on_generate_with_ai):
if st.button("+ Add Content", key="open_add_content_dialog_bottom"):
st.session_state['show_add_content_dialog'] = True
if st.session_state.get('show_add_content_dialog', False):
st.markdown("### Add Content")
with st.form("quick_add_form_dialog_bottom"):
title = st.text_input("Title")
platform = st.selectbox("Platform", ["Blog", "Instagram", "Twitter", "LinkedIn", "Facebook"])
content_type = st.selectbox("Content Type", ["Article", "Social Post", "Video", "Newsletter"])
publish_date = st.date_input("Publish Date", selected_date)
col_add, col_ai = st.columns([0.6, 0.4])
with col_add:
if st.form_submit_button("Add Content"):
on_add_content(title, platform, content_type, publish_date)
with col_ai:
if st.form_submit_button("Generate with AI"):
on_generate_with_ai(title, platform, content_type)
if st.button("Close", key="close_add_content_dialog_bottom"):
st.session_state['show_add_content_dialog'] = False

View File

@@ -0,0 +1,137 @@
import streamlit as st
def render_ai_suggestions_modal(generate_ai_suggestions, on_create_brief, on_schedule, on_refine, on_customize):
st.subheader("AI Content Suggestions")
default_type = st.session_state.get('ai_modal_type', "Blog Post")
default_topic = st.session_state.get('ai_modal_topic', "")
default_platform = st.session_state.get('ai_modal_platform', "Blog")
content_types = {
"Blog Post": "Long-form content for in-depth topics",
"Social Media Post": "Short, engaging content for social platforms",
"Video": "Visual content with script and storyboard",
"Newsletter": "Email content for subscriber engagement"
}
content_type = st.selectbox(
"Content Type",
list(content_types.keys()),
format_func=lambda x: f"{x} - {content_types[x]}",
key="modal_suggestion_type",
index=list(content_types.keys()).index(default_type) if default_type in content_types else 0
)
topic = st.text_input("Enter topic or keyword", value=default_topic, key="modal_suggestion_topic")
with st.expander("Advanced Options"):
audience = st.multiselect(
"Target Audience",
["Professionals", "Students", "Entrepreneurs", "General Public", "Industry Experts"],
default=["Professionals"]
)
goals = st.multiselect(
"Content Goals",
["Increase Engagement", "Generate Leads", "Build Authority", "Drive Traffic", "Educate"],
default=["Increase Engagement"]
)
tone = st.select_slider(
"Content Tone",
options=["Professional", "Casual", "Educational", "Entertaining", "Persuasive"],
value="Professional"
)
length = st.radio(
"Content Length",
["Short", "Medium", "Long"],
horizontal=True
)
st.subheader("AI Model Settings")
model_settings = {
"Creativity Level": st.slider("Creativity Level", 0.0, 1.0, 0.7, 0.1),
"Formality Level": st.slider("Formality Level", 0.0, 1.0, 0.5, 0.1),
"Technical Depth": st.slider("Technical Depth", 0.0, 1.0, 0.5, 0.1)
}
st.subheader("Content Style Preferences")
style_preferences = {
"Use Examples": st.checkbox("Include Real-world Examples", True),
"Use Statistics": st.checkbox("Include Statistics and Data", True),
"Use Quotes": st.checkbox("Include Expert Quotes", False),
"Use Case Studies": st.checkbox("Include Case Studies", False)
}
st.subheader("SEO Preferences")
seo_preferences = {
"Keyword Density": st.slider("Keyword Density (%)", 1, 5, 2),
"Internal Linking": st.checkbox("Suggest Internal Links", True),
"External Linking": st.checkbox("Suggest External Links", True),
"Meta Description": st.checkbox("Generate Meta Description", True)
}
st.subheader("Platform-specific Settings")
platform_settings = {
"Hashtag Usage": st.checkbox("Suggest Hashtags", True),
"Image Suggestions": st.checkbox("Suggest Images", True),
"Video Suggestions": st.checkbox("Suggest Videos", False),
"Interactive Elements": st.checkbox("Suggest Interactive Elements", False)
}
if st.button("Generate Suggestions", type="primary", key="modal_generate_btn"):
with st.spinner("Generating suggestions..."):
suggestions = generate_ai_suggestions(
content_type,
topic,
audience,
goals,
tone,
length,
model_settings,
style_preferences,
seo_preferences,
platform_settings
)
if suggestions:
suggestion_tabs = st.tabs([f"Suggestion {i+1}" for i in range(len(suggestions))])
for i, (tab, suggestion) in enumerate(zip(suggestion_tabs, suggestions)):
with tab:
col1, col2 = st.columns([2, 1])
with col1:
st.subheader(suggestion['title'])
st.write(f"**Type:** {suggestion['type']}")
st.write(f"**Platform:** {suggestion['platform']}")
st.write(f"**Target Audience:** {', '.join(suggestion['audience'])}")
st.write(f"**Estimated Impact:** {suggestion['impact']}")
with st.expander("Content Preview"):
st.write(suggestion.get('preview', 'Preview not available'))
if suggestion.get('style_elements'):
st.write("**Style Elements:**")
for element in suggestion['style_elements']:
st.write(f"- {element}")
if suggestion.get('seo_elements'):
st.write("**SEO Elements:**")
for element in suggestion['seo_elements']:
st.write(f"- {element}")
with col2:
st.subheader("Performance Metrics")
metrics = {
"Engagement Score": suggestion.get('engagement_score', '85%'),
"Reach Potential": suggestion.get('reach', 'High'),
"Conversion Rate": suggestion.get('conversion', '3.5%'),
"SEO Impact": suggestion.get('seo_impact', 'Strong')
}
for metric, value in metrics.items():
st.metric(metric, value)
st.subheader("Actions")
if st.button("Create Brief", key=f"modal_brief_{i}"):
on_create_brief(suggestion)
if st.button("Schedule", key=f"modal_schedule_{i}"):
on_schedule(suggestion)
if st.button("Refine", key=f"modal_refine_{i}"):
on_refine(suggestion)
if st.button("Customize", key=f"modal_customize_{i}"):
on_customize(suggestion)
with st.expander("Additional Options"):
st.write("**Platform Optimizations**")
for platform in suggestion.get('platform_optimizations', []):
st.write(f"- {platform}")
st.write("**Content Variations**")
for variation in suggestion.get('variations', []):
st.write(f"- {variation}")
st.write("**SEO Recommendations**")
for seo in suggestion.get('seo_recommendations', []):
st.write(f"- {seo}")
if suggestion.get('media_suggestions'):
st.write("**Media Suggestions**")
for media in suggestion['media_suggestions']:
st.write(f"- {media}")

View File

@@ -0,0 +1,51 @@
import streamlit as st
from .components.content_card import render_content_card
from .components.badge import render_badge
def render_calendar_view(calendar_data, icon_map, status_color, on_edit, on_delete, on_generate, get_item_key):
if calendar_data is not None and not calendar_data.empty:
st.markdown("### All Scheduled Content")
calendar_data = calendar_data.sort_values(by="date")
grouped = list(calendar_data.groupby(calendar_data['date'].dt.date))
for i, (date, group) in enumerate(grouped):
exp_open = (i == 0)
with st.expander(f"{date.strftime('%B %d, %Y')}", expanded=exp_open):
for idx, row in group.iterrows():
item_key = get_item_key(row)
is_editing = st.session_state.get("editing_item_key") == item_key
platform = str(row['platform'])
if hasattr(platform, 'value'):
platform = platform.value
platform_map = {
'blog': 'Blog',
'website': 'Blog',
'instagram': 'Instagram',
'twitter': 'Twitter',
'linkedin': 'LinkedIn',
'facebook': 'Facebook',
}
platform_disp = platform_map.get(platform.lower(), 'Blog')
type_disp = str(row['type'])
if hasattr(type_disp, 'value'):
type_disp = type_disp.value
type_disp = type_disp.replace('_', ' ').title()
status_disp = row['status'].capitalize()
platform_icon = icon_map.get(platform_disp, '🌐')
type_icon = icon_map.get(type_disp, '📄')
render_content_card(
row=row,
is_editing=is_editing,
on_edit=lambda r=row: on_edit(r),
on_delete=lambda r=row: on_delete(r),
on_generate=lambda r=row: on_generate(r),
icon_map=icon_map,
status_color=status_color,
platform_disp=platform_disp,
type_disp=type_disp,
status_disp=status_disp,
platform_icon=platform_icon,
type_icon=type_icon,
item_key=item_key
)
else:
st.info("No content scheduled yet. Add content to see it here.")

View File

@@ -0,0 +1,231 @@
import streamlit as st
from typing import Dict, Any, List
from lib.ai_seo_tools.content_calendar.models.calendar import ContentItem
import logging
logger = logging.getLogger(__name__)
def render_ab_testing(
content_generator,
calendar_manager
) -> None:
"""Render the A/B testing interface."""
try:
st.header("A/B Testing")
# Test Configuration
st.markdown("### Create A/B Test")
col1, col2 = st.columns([2, 1])
with col1:
test_content = st.selectbox(
"Select content for A/B testing",
options=[item.title for item in calendar_manager.get_calendar().get_all_content()],
key="ab_test_content_select"
)
with col2:
num_variants = st.slider(
"Number of variants",
min_value=2,
max_value=5,
value=2,
help="Number of different versions to test"
)
if test_content:
content_item = next(
item for item in calendar_manager.get_calendar().get_all_content()
if item.title == test_content
)
# Test Settings
with st.expander("Test Settings"):
col1, col2 = st.columns(2)
with col1:
test_duration = st.number_input(
"Test Duration (days)",
min_value=1,
max_value=30,
value=7
)
target_metric = st.selectbox(
"Primary Metric",
options=['Engagement', 'Conversion', 'Reach', 'Click-through'],
value='Engagement'
)
with col2:
audience_size = st.select_slider(
"Audience Size",
options=['Small', 'Medium', 'Large'],
value='Medium'
)
confidence_level = st.slider(
"Confidence Level",
min_value=90,
max_value=99,
value=95,
help="Statistical confidence level for test results"
)
# Generate Variants
if st.button("Generate Variants"):
with st.spinner("Generating variants..."):
variants = _generate_ab_test_variants(content_generator, content_item, num_variants)
if variants:
st.success(f"Generated {len(variants)} variants!")
# Display variants in tabs
variant_tabs = st.tabs([f"Variant {i+1}" for i in range(len(variants))])
for i, tab in enumerate(variant_tabs):
with tab:
st.markdown(f"### Variant {i+1}")
st.json(variants[i]['content'])
# Variant metrics
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
"Engagement Score",
f"{variants[i]['metrics']['engagement_score']:.1f}%"
)
with col2:
st.metric(
"Conversion Rate",
f"{variants[i]['metrics']['conversion_rate']:.1f}%"
)
with col3:
st.metric(
"Reach",
f"{variants[i]['metrics']['reach']:,}"
)
# Results Analysis
st.markdown("### Analyze Results")
if test_content in st.session_state.ab_test_results:
test_data = st.session_state.ab_test_results[test_content]
# Test Status
st.info(f"Test Status: {test_data['status']}")
st.write(f"Started: {test_data['start_time']}")
if test_data['status'] == 'running':
if st.button("End Test and Analyze"):
with st.spinner("Analyzing results..."):
results = _analyze_ab_test_results(content_item)
if results:
st.success("Analysis complete!")
_display_test_results(results)
except Exception as e:
logger.error(f"Error in A/B testing interface: {str(e)}", exc_info=True)
st.error(f"Error in A/B testing: {str(e)}")
def _generate_ab_test_variants(
content_generator,
content: ContentItem,
num_variants: int
) -> List[Dict[str, Any]]:
"""Generate A/B test variants for content."""
try:
logger.info(f"Generating {num_variants} variants for content: {content.title}")
# Convert content to dictionary format
content_dict = {
'title': content.title,
'content': content.description,
'metadata': {
'platform': content.platforms[0].name if content.platforms else 'Unknown',
'content_type': content.content_type.name
}
}
variants = []
for i in range(num_variants):
# Generate different variations
variant = content_generator.generate_variation(
content=content_dict,
variation_type=f"variant_{i+1}"
)
if variant:
variants.append(variant)
return variants
except Exception as e:
logger.error(f"Error generating variants: {str(e)}")
return []
def _analyze_ab_test_results(content_item: ContentItem) -> Dict[str, Any]:
"""Analyze results of A/B testing for content optimization."""
try:
logger.info(f"Analyzing A/B test results for: {content_item.title}")
if content_item.title not in st.session_state.ab_test_results:
raise ValueError("No A/B test results found for this content")
test_data = st.session_state.ab_test_results[content_item.title]
variants = test_data['variants']
# Calculate performance metrics
results = {
'total_engagement': sum(v['metrics']['engagement_score'] for v in variants),
'total_conversions': sum(v['metrics']['conversion_rate'] for v in variants),
'total_reach': sum(v['metrics']['reach'] for v in variants),
'best_performing_variant': max(variants, key=lambda x: x['metrics']['engagement_score']),
'recommendations': []
}
# Generate recommendations
for variant in variants:
if variant['metrics']['engagement_score'] > 0.7: # High engagement threshold
results['recommendations'].append({
'variant_id': variant['variant_id'],
'reason': 'High engagement score',
'suggested_actions': ['Scale this variant', 'Apply learnings to other content']
})
# Update test status
test_data['status'] = 'completed'
test_data['results'] = results
logger.info("A/B test results analyzed successfully")
return results
except Exception as e:
logger.error(f"Error analyzing A/B test results: {str(e)}", exc_info=True)
st.error(f"Error analyzing A/B test results: {str(e)}")
return {}
def _display_test_results(results: Dict[str, Any]) -> None:
"""Display A/B test results in the UI."""
with st.expander("Overall Performance", expanded=True):
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
"Total Engagement",
f"{results['total_engagement']:.1f}%"
)
with col2:
st.metric(
"Total Conversions",
f"{results['total_conversions']:.1f}%"
)
with col3:
st.metric(
"Total Reach",
f"{results['total_reach']:,}"
)
with st.expander("Best Performing Variant", expanded=True):
best_variant = results['best_performing_variant']
st.markdown(f"### {best_variant['variant_id']}")
st.json(best_variant['content'])
with st.expander("Recommendations", expanded=True):
for rec in results['recommendations']:
st.markdown(f"#### {rec['variant_id']}")
st.write(f"Reason: {rec['reason']}")
st.write("Suggested Actions:")
for action in rec['suggested_actions']:
st.write(f"- {action}")

View File

@@ -0,0 +1,2 @@
def render_badge(platform_disp, platform_icon, type_disp, status_disp):
return f"<span class='badge-content-calendar badge-platform-{platform_disp.lower()}'>{platform_icon} {platform_disp} &nbsp;|&nbsp; {type_disp} &nbsp;|&nbsp; <span class='chip-status chip-status-{status_disp.lower()}'>{status_disp}</span></span>"

View File

@@ -0,0 +1,22 @@
import streamlit as st
def render_content_card(row, is_editing, on_edit, on_delete, on_generate, icon_map, status_color, platform_disp, type_disp, status_disp, platform_icon, type_icon, item_key):
st.markdown(f"<div class='card-content-calendar'>", unsafe_allow_html=True)
st.markdown(f"<div style='display:flex;align-items:center;justify-content:space-between;gap:8px;'>", unsafe_allow_html=True)
st.markdown(f"<div style='display:flex;align-items:center;gap:8px;min-width:0;flex:1;'>"
f"{type_icon}<span class='content-title'>{row['title']}</span></div>", unsafe_allow_html=True)
st.markdown("<div style='display:flex;align-items:center;gap:4px;'>", unsafe_allow_html=True)
col1, col2, col3 = st.columns([1, 1, 1])
with col1:
if st.button("", key=f"generate_{item_key}", help="Generate with AI Blog Writer", use_container_width=True):
on_generate()
with col2:
if st.button("✏️", key=f"edit_{item_key}", help="Edit Content", use_container_width=True):
on_edit()
with col3:
if st.button("🗑️", key=f"delete_{item_key}", help="Delete Content", use_container_width=True):
on_delete()
st.markdown("</div>", unsafe_allow_html=True)
st.markdown("</div>", unsafe_allow_html=True)
st.markdown(f"<div class='content-meta'><span class='badge-content-calendar badge-platform-{platform_disp.lower()}'>{platform_icon} {platform_disp} &nbsp;|&nbsp; {type_disp} &nbsp;|&nbsp; <span class='chip-status chip-status-{status_disp.lower()}'>{status_disp}</span></span></div>", unsafe_allow_html=True)
st.markdown("</div>", unsafe_allow_html=True)

View File

@@ -0,0 +1,467 @@
import streamlit as st
from typing import Dict, Any, List
from datetime import datetime
import pandas as pd
from ...core.content_generator import ContentGenerator
from ...core.ai_generator import AIGenerator
from ...integrations.seo_optimizer import SEOOptimizer
from ...models.calendar import ContentItem, ContentType, Platform, SEOData
import logging
logger = logging.getLogger('content_calendar.optimization')
class OptimizationManager:
def __init__(self):
if 'optimization_history' not in st.session_state:
st.session_state.optimization_history = {}
if 'optimization_previews' not in st.session_state:
st.session_state.optimization_previews = {}
if 'optimization_metrics' not in st.session_state:
st.session_state.optimization_metrics = {}
def track_optimization(self, content_id: str, optimization_data: Dict[str, Any]) -> bool:
"""Track optimization changes for content with detailed metrics."""
try:
if content_id not in st.session_state.optimization_history:
st.session_state.optimization_history[content_id] = []
optimization_data['timestamp'] = datetime.now()
optimization_data['metrics'] = self._calculate_optimization_metrics(optimization_data)
st.session_state.optimization_history[content_id].append(optimization_data)
# Update metrics
if content_id not in st.session_state.optimization_metrics:
st.session_state.optimization_metrics[content_id] = []
st.session_state.optimization_metrics[content_id].append(optimization_data['metrics'])
return True
except Exception as e:
logger.error(f"Error tracking optimization: {str(e)}")
return False
def _calculate_optimization_metrics(self, optimization_data: Dict[str, Any]) -> Dict[str, Any]:
"""Calculate detailed optimization metrics."""
try:
metrics = {
'readability_score': 0,
'seo_score': 0,
'engagement_potential': 0,
'keyword_density': 0,
'content_quality': 0
}
# Calculate readability score
if 'content' in optimization_data:
content = optimization_data['content']
metrics['readability_score'] = self._calculate_readability(content)
# Calculate SEO score
if 'seo_data' in optimization_data:
seo_data = optimization_data['seo_data']
metrics['seo_score'] = self._calculate_seo_score(seo_data)
metrics['keyword_density'] = self._calculate_keyword_density(seo_data)
# Calculate engagement potential
if 'engagement_metrics' in optimization_data:
engagement = optimization_data['engagement_metrics']
metrics['engagement_potential'] = self._calculate_engagement_potential(engagement)
# Calculate overall content quality
metrics['content_quality'] = (
metrics['readability_score'] * 0.3 +
metrics['seo_score'] * 0.3 +
metrics['engagement_potential'] * 0.4
)
return metrics
except Exception as e:
logger.error(f"Error calculating optimization metrics: {str(e)}")
return {}
def _calculate_readability(self, content: str) -> float:
"""Calculate content readability score."""
try:
# Implement readability calculation logic
# This is a placeholder implementation
return 0.8
except Exception as e:
logger.error(f"Error calculating readability: {str(e)}")
return 0.0
def _calculate_seo_score(self, seo_data: SEOData) -> float:
"""Calculate SEO optimization score."""
try:
# Implement SEO score calculation logic
# This is a placeholder implementation
return 0.85
except Exception as e:
logger.error(f"Error calculating SEO score: {str(e)}")
return 0.0
def _calculate_keyword_density(self, seo_data: SEOData) -> float:
"""Calculate keyword density."""
try:
# Implement keyword density calculation logic
# This is a placeholder implementation
return 2.5
except Exception as e:
logger.error(f"Error calculating keyword density: {str(e)}")
return 0.0
def _calculate_engagement_potential(self, engagement: Dict[str, Any]) -> float:
"""Calculate content engagement potential."""
try:
# Implement engagement potential calculation logic
# This is a placeholder implementation
return 0.75
except Exception as e:
logger.error(f"Error calculating engagement potential: {str(e)}")
return 0.0
def get_optimization_history(self, content_id: str) -> List[Dict[str, Any]]:
"""Get detailed optimization history for content."""
return st.session_state.optimization_history.get(content_id, [])
def get_optimization_metrics(self, content_id: str) -> List[Dict[str, Any]]:
"""Get optimization metrics history."""
return st.session_state.optimization_metrics.get(content_id, [])
def save_preview(self, content_id: str, preview_data: Dict[str, Any]) -> bool:
"""Save optimization preview with versioning."""
try:
if content_id not in st.session_state.optimization_previews:
st.session_state.optimization_previews[content_id] = []
preview_data['version'] = len(st.session_state.optimization_previews[content_id]) + 1
preview_data['timestamp'] = datetime.now()
st.session_state.optimization_previews[content_id].append(preview_data)
return True
except Exception as e:
logger.error(f"Error saving preview: {str(e)}")
return False
def get_preview(self, content_id: str, version: int = None) -> Dict[str, Any]:
"""Get optimization preview with optional versioning."""
try:
previews = st.session_state.optimization_previews.get(content_id, [])
if not previews:
return {}
if version is None:
return previews[-1]
for preview in previews:
if preview['version'] == version:
return preview
return {}
except Exception as e:
logger.error(f"Error getting preview: {str(e)}")
return {}
def render_content_optimization(
content_generator: ContentGenerator,
ai_generator: AIGenerator,
seo_optimizer: SEOOptimizer
):
"""Render the content optimization interface with advanced features."""
st.header("Content Optimization")
# Initialize optimization manager
optimization_manager = OptimizationManager()
# Check if calendar manager is available
if 'calendar_manager' not in st.session_state:
st.error("Calendar manager not initialized. Please refresh the page.")
return
# Get available content
try:
available_content = st.session_state.calendar_manager.get_calendar().get_all_content()
content_options = [item.title for item in available_content]
except Exception as e:
logger.error(f"Error getting content options: {str(e)}")
st.error("Error loading content. Please try again.")
return
if not content_options:
st.info("No content available for optimization. Please add some content first.")
return
# Content Selection
selected_content = st.selectbox(
"Select content to optimize",
options=content_options,
key="optimize_content_select"
)
if selected_content:
try:
content_item = next(
item for item in available_content
if item.title == selected_content
)
# Create tabs for different optimization aspects
opt_tabs = st.tabs(["Content Optimization", "SEO Optimization", "Preview", "History", "Analytics"])
with opt_tabs[0]:
st.subheader("Content Optimization")
# Advanced Optimization Settings
with st.expander("Advanced Settings", expanded=True):
col1, col2 = st.columns(2)
with col1:
tone = st.select_slider(
"Content Tone",
options=['Professional', 'Casual', 'Friendly', 'Authoritative', 'Conversational'],
value='Professional'
)
length = st.select_slider(
"Content Length",
options=['Short', 'Medium', 'Long', 'Comprehensive'],
value='Medium'
)
with col2:
engagement_goal = st.select_slider(
"Engagement Goal",
options=['Awareness', 'Consideration', 'Conversion', 'Retention'],
value='Consideration'
)
creativity_level = st.slider(
"Creativity Level",
min_value=1,
max_value=10,
value=5
)
# Platform-Specific Optimization
st.subheader("Platform-Specific Optimization")
platforms = st.multiselect(
"Target Platforms",
options=[p.name for p in content_item.platforms],
default=[p.name for p in content_item.platforms]
)
# Generate Optimization
if st.button("Generate Optimization"):
with st.spinner("Generating optimization..."):
try:
# Generate optimized content
optimized_content = content_generator.optimize_for_platform(
content=content_item,
platform=Platform[platforms[0]] if platforms else content_item.platforms[0],
requirements={
'tone': tone,
'length': length,
'engagement_goal': engagement_goal,
'creativity_level': creativity_level
}
)
if optimized_content:
# Track optimization
optimization_manager.track_optimization(
content_item.title,
{
'type': 'content',
'changes': optimized_content.get('changes', []),
'metrics': optimized_content.get('metrics', {}),
'content': optimized_content.get('content', ''),
'engagement_metrics': optimized_content.get('engagement_metrics', {})
}
)
# Save preview
optimization_manager.save_preview(
content_item.title,
{
'original': content_item.description,
'optimized': optimized_content.get('content', ''),
'changes': optimized_content.get('changes', []),
'metrics': optimized_content.get('metrics', {})
}
)
st.success("Content optimized successfully!")
except Exception as e:
logger.error(f"Error optimizing content: {str(e)}")
st.error(f"Error optimizing content: {str(e)}")
with opt_tabs[1]:
st.subheader("SEO Optimization")
# SEO Settings
with st.expander("SEO Settings", expanded=True):
col1, col2 = st.columns(2)
with col1:
keyword_density = st.slider(
"Target Keyword Density",
min_value=1,
max_value=5,
value=2,
help="Target percentage of keywords in content"
)
internal_linking = st.checkbox(
"Enable Internal Linking",
value=True,
help="Automatically add internal links to related content"
)
with col2:
external_linking = st.checkbox(
"Enable External Linking",
value=True,
help="Add relevant external links for credibility"
)
structured_data = st.checkbox(
"Add Structured Data",
value=True,
help="Include schema.org structured data"
)
# Generate SEO Optimization
if st.button("Generate SEO Optimization"):
with st.spinner("Generating SEO optimization..."):
try:
# Generate SEO-optimized content
seo_optimized = seo_optimizer.optimize_content(
content=content_item,
content_type=content_item.content_type.name,
language='English',
search_intent='Informational Intent',
settings={
'keyword_density': keyword_density,
'internal_linking': internal_linking,
'external_linking': external_linking,
'structured_data': structured_data
}
)
if seo_optimized:
# Track optimization
optimization_manager.track_optimization(
content_item.title,
{
'type': 'seo',
'changes': seo_optimized.get('changes', []),
'metrics': seo_optimized.get('metrics', {}),
'seo_data': seo_optimized
}
)
# Save preview
optimization_manager.save_preview(
content_item.title,
{
'meta_description': seo_optimized.get('meta_description', ''),
'keywords': seo_optimized.get('keywords', []),
'structured_data': seo_optimized.get('structured_data', {}),
'changes': seo_optimized.get('changes', [])
}
)
st.success("SEO optimization completed!")
except Exception as e:
logger.error(f"Error optimizing SEO: {str(e)}")
st.error(f"Error optimizing SEO: {str(e)}")
with opt_tabs[2]:
st.subheader("Optimization Preview")
preview_data = optimization_manager.get_preview(content_item.title)
if preview_data:
# Content Preview
if 'original' in preview_data:
st.markdown("### Content Changes")
col1, col2 = st.columns(2)
with col1:
st.markdown("#### Original Content")
st.write(preview_data['original'])
with col2:
st.markdown("#### Optimized Content")
st.write(preview_data['optimized'])
st.markdown("#### Key Changes")
for change in preview_data.get('changes', []):
st.write(f"- {change}")
# SEO Preview
if 'meta_description' in preview_data:
st.markdown("### SEO Changes")
st.markdown("#### Meta Description")
st.write(preview_data['meta_description'])
st.markdown("#### Keywords")
st.write(", ".join(preview_data['keywords']))
st.markdown("#### Structured Data")
st.json(preview_data['structured_data'])
# Metrics Preview
if 'metrics' in preview_data:
st.markdown("### Optimization Metrics")
metrics = preview_data['metrics']
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Readability Score", f"{metrics.get('readability_score', 0):.1%}")
with col2:
st.metric("SEO Score", f"{metrics.get('seo_score', 0):.1%}")
with col3:
st.metric("Engagement Potential", f"{metrics.get('engagement_potential', 0):.1%}")
else:
st.info("No optimization preview available. Generate optimization first.")
with opt_tabs[3]:
st.subheader("Optimization History")
history = optimization_manager.get_optimization_history(content_item.title)
if history:
for entry in history:
with st.expander(f"Optimization at {entry['timestamp']}"):
st.write(f"Type: {entry['type']}")
st.write("Changes:")
for change in entry.get('changes', []):
st.write(f"- {change}")
if 'metrics' in entry:
st.write("Metrics:")
st.json(entry['metrics'])
else:
st.info("No optimization history available.")
with opt_tabs[4]:
st.subheader("Optimization Analytics")
metrics_history = optimization_manager.get_optimization_metrics(content_item.title)
if metrics_history:
# Convert metrics history to DataFrame
df = pd.DataFrame(metrics_history)
# Plot metrics over time
st.line_chart(df[['readability_score', 'seo_score', 'engagement_potential', 'content_quality']])
# Display current metrics
current_metrics = metrics_history[-1]
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Readability", f"{current_metrics.get('readability_score', 0):.1%}")
with col2:
st.metric("SEO Score", f"{current_metrics.get('seo_score', 0):.1%}")
with col3:
st.metric("Engagement", f"{current_metrics.get('engagement_potential', 0):.1%}")
with col4:
st.metric("Overall Quality", f"{current_metrics.get('content_quality', 0):.1%}")
# Display keyword density trend
st.subheader("Keyword Density Trend")
st.line_chart(df['keyword_density'])
else:
st.info("No optimization metrics available. Generate optimization first.")

View File

@@ -0,0 +1,392 @@
import streamlit as st
from typing import Dict, Any, List
from datetime import datetime, timedelta
import pandas as pd
from ...core.content_generator import ContentGenerator
from ...core.ai_generator import AIGenerator
from ...integrations.seo_optimizer import SEOOptimizer
from ...models.calendar import ContentItem, ContentType, Platform, SEOData
import logging
logger = logging.getLogger('content_calendar.series')
class SeriesManager:
def __init__(self):
self.series_data = {}
if 'content_series' not in st.session_state:
st.session_state.content_series = {}
if 'series_relationships' not in st.session_state:
st.session_state.series_relationships = {}
if 'series_performance' not in st.session_state:
st.session_state.series_performance = {}
def create_series(self, series_id: str, topic: str, num_pieces: int, content_type: ContentType,
platforms: List[Platform], schedule_strategy: str = 'linear') -> Dict[str, Any]:
"""Create a new content series with tracking and scheduling."""
try:
series = {
'id': series_id,
'topic': topic,
'num_pieces': num_pieces,
'content_type': content_type,
'platforms': platforms,
'schedule_strategy': schedule_strategy,
'pieces': [],
'performance': {},
'created_at': datetime.now(),
'status': 'draft',
'relationships': {},
'platform_distribution': {p.name: [] for p in platforms}
}
st.session_state.content_series[series_id] = series
return series
except Exception as e:
logger.error(f"Error creating series: {str(e)}")
return None
def add_piece(self, series_id: str, piece: Dict[str, Any]) -> bool:
"""Add a content piece to the series with relationship tracking."""
try:
if series_id in st.session_state.content_series:
series = st.session_state.content_series[series_id]
piece_id = f"piece_{len(series['pieces'])}"
piece['id'] = piece_id
# Track relationships
if series['pieces']:
previous_piece = series['pieces'][-1]
piece['relationships'] = {
'previous': previous_piece['id'],
'next': None
}
previous_piece['relationships']['next'] = piece_id
# Add to platform distribution
for platform in piece.get('platforms', []):
if platform.name in series['platform_distribution']:
series['platform_distribution'][platform.name].append(piece_id)
series['pieces'].append(piece)
return True
return False
except Exception as e:
logger.error(f"Error adding piece to series: {str(e)}")
return False
def get_series_performance(self, series_id: str) -> Dict[str, Any]:
"""Get comprehensive performance analytics for a series."""
try:
if series_id in st.session_state.content_series:
series = st.session_state.content_series[series_id]
performance = {
'overall': {
'total_engagement': 0,
'total_reach': 0,
'conversion_rate': 0,
'average_engagement': 0
},
'platforms': {},
'pieces': {},
'trends': {
'engagement': [],
'reach': [],
'conversions': []
}
}
# Calculate overall metrics
for piece in series['pieces']:
piece_performance = piece.get('performance', {})
performance['overall']['total_engagement'] += piece_performance.get('engagement', 0)
performance['overall']['total_reach'] += piece_performance.get('reach', 0)
performance['overall']['conversion_rate'] += piece_performance.get('conversion_rate', 0)
# Track piece-specific performance
performance['pieces'][piece['id']] = piece_performance
# Track trends
performance['trends']['engagement'].append(piece_performance.get('engagement', 0))
performance['trends']['reach'].append(piece_performance.get('reach', 0))
performance['trends']['conversions'].append(piece_performance.get('conversion_rate', 0))
# Calculate averages
num_pieces = len(series['pieces'])
if num_pieces > 0:
performance['overall']['average_engagement'] = performance['overall']['total_engagement'] / num_pieces
performance['overall']['conversion_rate'] = performance['overall']['conversion_rate'] / num_pieces
# Calculate platform-specific performance
for platform in series['platforms']:
platform_pieces = series['platform_distribution'].get(platform.name, [])
platform_performance = {
'engagement': 0,
'reach': 0,
'conversion_rate': 0
}
for piece_id in platform_pieces:
piece_performance = performance['pieces'].get(piece_id, {})
platform_performance['engagement'] += piece_performance.get('engagement', 0)
platform_performance['reach'] += piece_performance.get('reach', 0)
platform_performance['conversion_rate'] += piece_performance.get('conversion_rate', 0)
if platform_pieces:
platform_performance['engagement'] /= len(platform_pieces)
platform_performance['conversion_rate'] /= len(platform_pieces)
performance['platforms'][platform.name] = platform_performance
return performance
return {}
except Exception as e:
logger.error(f"Error getting series performance: {str(e)}")
return {}
def update_series_status(self, series_id: str, status: str) -> bool:
"""Update the status of a series."""
try:
if series_id in st.session_state.content_series:
st.session_state.content_series[series_id]['status'] = status
return True
return False
except Exception as e:
logger.error(f"Error updating series status: {str(e)}")
return False
def schedule_series(self, series_id: str, start_date: datetime, interval: int = 7) -> bool:
"""Schedule the series content with flexible scheduling strategies."""
try:
if series_id in st.session_state.content_series:
series = st.session_state.content_series[series_id]
current_date = start_date
for piece in series['pieces']:
piece['scheduled_date'] = current_date
if series['schedule_strategy'] == 'linear':
current_date += timedelta(days=interval)
elif series['schedule_strategy'] == 'burst':
current_date += timedelta(days=1)
elif series['schedule_strategy'] == 'custom':
# Custom scheduling is handled by the UI
pass
return True
return False
except Exception as e:
logger.error(f"Error scheduling series: {str(e)}")
return False
def render_content_series_generator(ai_generator: AIGenerator, content_generator: ContentGenerator,
seo_optimizer: SEOOptimizer):
"""Render the content series generator interface with enhanced features."""
st.header("Content Series Generator")
# Initialize series manager
series_manager = SeriesManager()
# Series Creation Form
with st.form("series_creation_form"):
st.subheader("Create New Series")
series_topic = st.text_input("Series Topic")
num_pieces = st.slider("Number of pieces", 2, 10, 3)
content_type = st.selectbox(
"Content Type",
options=[ct.name for ct in ContentType],
key="series_content_type"
)
# Multi-platform selection
platforms = st.multiselect(
"Target Platforms",
options=[p.name for p in Platform],
default=['WEBSITE'],
key="series_platforms"
)
# Schedule strategy
schedule_strategy = st.selectbox(
"Schedule Strategy",
options=['linear', 'burst', 'custom'],
help="Linear: Evenly spaced, Burst: Grouped together, Custom: Manual scheduling"
)
# Series metadata
with st.expander("Series Metadata"):
target_audience = st.text_area("Target Audience")
series_goals = st.multiselect(
"Series Goals",
options=['Awareness', 'Engagement', 'Conversion', 'Education'],
default=['Awareness']
)
series_tone = st.select_slider(
"Series Tone",
options=['Professional', 'Casual', 'Friendly', 'Authoritative', 'Conversational'],
value='Professional'
)
submitted = st.form_submit_button("Generate Series")
if submitted and series_topic:
with st.spinner("Generating content series..."):
try:
# Create series
series_id = f"series_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
series = series_manager.create_series(
series_id=series_id,
topic=series_topic,
num_pieces=num_pieces,
content_type=ContentType[content_type],
platforms=[Platform[p] for p in platforms],
schedule_strategy=schedule_strategy
)
if series:
# Generate series content
for i in range(num_pieces):
content_item = ContentItem(
title=f"{series_topic} - Part {i+1}",
description="",
content_type=ContentType[content_type],
platforms=[Platform[p] for p in platforms],
publish_date=datetime.now() + timedelta(days=i*7),
seo_data=SEOData(
title=f"{series_topic} - Part {i+1}",
meta_description="",
keywords=[],
structured_data={}
),
status='Draft'
)
# Generate content using AI
base_content = ai_generator.generate_series_content(
content_item=content_item,
series_info={
'topic': series_topic,
'part_number': i+1,
'total_parts': num_pieces,
'content_type': content_type,
'platforms': platforms,
'audience': target_audience,
'goals': series_goals,
'tone': series_tone
}
)
if base_content:
# Enhance with Content Generator
enhanced_content = content_generator.enhance_series_content(
content=base_content,
series_info={
'topic': series_topic,
'part_number': i+1,
'total_parts': num_pieces
}
)
if enhanced_content:
base_content.update(enhanced_content)
# Add to series
series_manager.add_piece(series_id, {
'part_number': i+1,
'content': base_content,
'seo_data': seo_optimizer.optimize_content(
content=base_content,
content_type=content_type,
language='English',
search_intent='Informational Intent'
)
})
st.success(f"Generated {num_pieces} content pieces for series!")
# Display series preview
with st.expander("Series Preview", expanded=True):
for piece in series_manager.series_data[series_id]['pieces']:
st.markdown(f"### Part {piece['part_number']}")
st.json(piece['content'])
# Platform-specific previews
st.markdown("#### Platform Previews")
for platform in platforms:
with st.expander(f"{platform} Preview"):
st.write(piece['content'].get('platform_previews', {}).get(platform, 'No preview available'))
# Series scheduling
st.subheader("Series Scheduling")
if schedule_strategy == 'linear':
start_date = st.date_input("Start Date", datetime.now())
interval = st.number_input("Days between pieces", min_value=1, value=7)
if st.button("Schedule Series"):
series_manager.schedule_series(series_id, start_date, interval)
st.success("Series scheduled successfully!")
elif schedule_strategy == 'burst':
start_date = st.date_input("Start Date", datetime.now())
if st.button("Schedule Series"):
series_manager.schedule_series(series_id, start_date, interval=1)
st.success("Series scheduled successfully!")
else: # custom
for i, piece in enumerate(series_manager.series_data[series_id]['pieces']):
piece['scheduled_date'] = st.date_input(
f"Publish Date for Part {i+1}",
datetime.now() + timedelta(days=i*7)
)
if st.button("Save Schedule"):
st.success("Series schedule saved!")
# Series performance tracking
st.subheader("Series Performance")
performance_data = series_manager.get_series_performance(series_id)
if performance_data:
st.write("### Overall Performance")
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Total Engagement", f"{performance_data['overall']['total_engagement']:.1f}%")
with col2:
st.metric("Total Reach", f"{performance_data['overall']['total_reach']:,}")
with col3:
st.metric("Conversion Rate", f"{performance_data['overall']['conversion_rate']:.1f}%")
# Platform-specific performance
st.write("### Platform Performance")
for platform in platforms:
with st.expander(f"{platform} Performance"):
platform_data = performance_data['platforms'].get(platform, {})
st.write(f"Engagement: {platform_data.get('engagement', 0):.1f}%")
st.write(f"Reach: {platform_data.get('reach', 0):,}")
st.write(f"Conversions: {platform_data.get('conversion_rate', 0):.1f}%")
# Performance trends
st.write("### Performance Trends")
trend_data = performance_data['trends']
st.line_chart(pd.DataFrame({
'Engagement': trend_data['engagement'],
'Reach': trend_data['reach'],
'Conversions': trend_data['conversions']
}))
except Exception as e:
logger.error(f"Error generating series: {str(e)}", exc_info=True)
st.error(f"Error generating series: {str(e)}")
# Display existing series
if st.session_state.content_series:
st.subheader("Existing Series")
for series_id, series in st.session_state.content_series.items():
with st.expander(f"Series: {series['topic']}"):
st.write(f"Status: {series['status']}")
st.write(f"Pieces: {len(series['pieces'])}")
st.write(f"Created: {series['created_at']}")
# Series actions
if st.button(f"View Details", key=f"view_{series_id}"):
st.session_state.selected_series = series_id
if st.button(f"Delete Series", key=f"delete_{series_id}"):
del st.session_state.content_series[series_id]
st.experimental_rerun()

View File

@@ -0,0 +1,81 @@
import streamlit as st
from typing import Dict, Any
from lib.ai_seo_tools.content_calendar.models.calendar import ContentItem
import logging
logger = logging.getLogger(__name__)
def render_performance_insights(content_item: ContentItem, platform_adapter) -> None:
"""Render performance insights for a content item."""
try:
logger.info(f"Rendering performance insights for: {content_item.title}")
# Get performance data from platform adapter
performance_data = platform_adapter.get_content_performance(content_item)
if not performance_data:
st.warning("No performance data available for this content")
return
# Create metrics section
st.subheader("Performance Metrics")
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
"Engagement Rate",
f"{performance_data.get('engagement_rate', 0):.1f}%",
f"{performance_data.get('engagement_rate_change', 0):+.1f}%"
)
with col2:
st.metric(
"Reach",
f"{performance_data.get('reach', 0):,}",
f"{performance_data.get('reach_change', 0):+,}"
)
with col3:
st.metric(
"Conversion Rate",
f"{performance_data.get('conversion_rate', 0):.1f}%",
f"{performance_data.get('conversion_rate_change', 0):+.1f}%"
)
# Create audience insights section
st.subheader("Audience Insights")
audience_data = performance_data.get('audience_insights', {})
if audience_data:
col1, col2 = st.columns(2)
with col1:
st.write("Demographics")
st.write(f"- Age: {audience_data.get('age_range', 'N/A')}")
st.write(f"- Gender: {audience_data.get('gender', 'N/A')}")
st.write(f"- Location: {audience_data.get('location', 'N/A')}")
with col2:
st.write("Behavior")
st.write(f"- Peak Time: {audience_data.get('peak_time', 'N/A')}")
st.write(f"- Device: {audience_data.get('device', 'N/A')}")
st.write(f"- Platform: {audience_data.get('platform', 'N/A')}")
# Create content insights section
st.subheader("Content Insights")
content_insights = performance_data.get('content_insights', {})
if content_insights:
st.write("Top Performing Elements")
for element, score in content_insights.get('top_elements', {}).items():
st.write(f"- {element}: {score}")
st.write("Improvement Suggestions")
for suggestion in content_insights.get('suggestions', []):
st.write(f"- {suggestion}")
logger.info(f"Performance insights rendered successfully for: {content_item.title}")
except Exception as e:
logger.error(f"Error rendering performance insights: {str(e)}", exc_info=True)
st.error(f"Error rendering performance insights: {str(e)}")

View File

@@ -0,0 +1,634 @@
import streamlit as st
import pandas as pd
from datetime import datetime
import logging
import sys
import hashlib
from .calendar_view import render_calendar_view
from .filters import render_filters
from .add_content_modal import render_add_content_modal
from .ai_suggestions_modal import render_ai_suggestions_modal
from .components.performance_insights import render_performance_insights
from .components.content_series import render_content_series_generator
from .components.ab_testing import render_ab_testing
from .components.content_optimization import render_content_optimization
from ..core.calendar_manager import CalendarManager
from ..core.content_brief import ContentBriefGenerator
from ..core.content_generator import ContentGenerator
from ..core.ai_generator import AIGenerator
from ..integrations.platform_adapters import UnifiedPlatformAdapter
from ..integrations.seo_optimizer import SEOOptimizer
from lib.ai_seo_tools.content_calendar.models.calendar import ContentItem, Platform, ContentType, SEOData, Calendar
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from typing import Dict, Any, List, Tuple
import json
class ContentCalendarDashboard:
"""Interactive dashboard for content calendar management."""
def __init__(self):
self.logger = logging.getLogger('content_calendar.dashboard')
self.logger.info("Initializing ContentCalendarDashboard")
# Initialize calendar manager and store in session state
if 'calendar_manager' not in st.session_state:
st.session_state.calendar_manager = CalendarManager()
st.session_state.calendar_manager.load_calendar_from_json()
self.calendar_manager = st.session_state.calendar_manager
self.content_brief_generator = ContentBriefGenerator()
self.content_generator = ContentGenerator()
self.ai_generator = AIGenerator()
self.platform_adapter = UnifiedPlatformAdapter()
self.seo_optimizer = SEOOptimizer()
# Initialize A/B testing state
if 'ab_test_results' not in st.session_state:
st.session_state.ab_test_results = {}
# Initialize content optimization state
if 'optimization_history' not in st.session_state:
st.session_state.optimization_history = {}
# Ensure a calendar exists
if not self.calendar_manager.get_calendar():
self.calendar_manager._calendar = Calendar(
start_date=datetime.now(),
duration='monthly',
platforms=[Platform.WEBSITE, Platform.INSTAGRAM, Platform.TWITTER, Platform.LINKEDIN, Platform.FACEBOOK],
schedule={}
)
# Initialize session state
if 'calendar_data' not in st.session_state:
st.session_state.calendar_data = None
if 'selected_content' not in st.session_state:
st.session_state.selected_content = None
if 'view_mode' not in st.session_state:
st.session_state.view_mode = 'day'
if 'selected_date' not in st.session_state:
st.session_state.selected_date = datetime.now()
self.logger.info("ContentCalendarDashboard initialized successfully")
def render(self):
self.logger.info("Starting dashboard render (tabbed UI)")
try:
self._inject_custom_css()
st.title("AI Content Planning")
st.markdown("""
Plan, schedule, and manage your content strategy with AI-powered insights. Use the calendar to organize your content and leverage AI tools for optimization.
""")
tabs = st.tabs(["Content Planning", "Content Optimization", "A/B Testing", "Content Series", "Analytics"])
with tabs[0]:
icon_map = {
'Blog': '📝', 'Website': '🌐', 'Instagram': '📸', 'Twitter': '🐦', 'LinkedIn': '💼', 'Facebook': '📘',
'Article': '📄', 'Social Post': '💬', 'Video': '🎬', 'Newsletter': '✉️'
}
status_color = {
'Draft': '#bdbdbd', 'Scheduled': '#1976d2', 'Published': '#43a047', 'Archived': '#757575'
}
calendar_data = self._get_calendar_data()
def on_edit(row):
st.session_state["editing_item_key"] = self._get_item_key(row)
st.experimental_rerun()
def on_delete(row):
self._delete_content(row)
st.experimental_rerun()
def on_generate(row):
st.session_state['show_ai_modal'] = True
st.session_state['ai_modal_topic'] = row['title']
st.session_state['ai_modal_type'] = str(row['type'])
st.session_state['ai_modal_platform'] = str(row['platform'])
st.experimental_rerun()
render_calendar_view(
calendar_data=calendar_data,
icon_map=icon_map,
status_color=status_color,
on_edit=on_edit,
on_delete=on_delete,
on_generate=on_generate,
get_item_key=self._get_item_key
)
st.markdown("---")
render_filters()
def handle_add_content(title, platform, content_type, publish_date):
self._add_content({
'title': title,
'platform': platform,
'type': content_type,
'publish_date': publish_date
})
st.session_state['show_add_content_dialog'] = False
st.success("Content added!")
st.experimental_rerun()
def handle_generate_with_ai(title, platform, content_type):
st.session_state['show_add_content_dialog'] = False
st.session_state['show_ai_modal'] = True
st.session_state['ai_modal_topic'] = title
st.session_state['ai_modal_type'] = content_type
st.session_state['ai_modal_platform'] = platform
render_add_content_modal(
selected_date=st.session_state.selected_date,
on_add_content=handle_add_content,
on_generate_with_ai=handle_generate_with_ai
)
if st.session_state.get('show_ai_modal', False):
st.markdown("### AI Content Suggestions")
with st.container():
render_ai_suggestions_modal(
generate_ai_suggestions=self._generate_ai_suggestions,
on_create_brief=self._create_content_brief,
on_schedule=self._schedule_content,
on_refine=self._refine_suggestion,
on_customize=self._customize_suggestion
)
if st.button("Close"):
st.session_state['show_ai_modal'] = False
with tabs[1]:
render_content_optimization(
content_generator=self.content_generator,
ai_generator=self.ai_generator,
seo_optimizer=self.seo_optimizer
)
with tabs[2]:
render_ab_testing(self.content_generator, self.calendar_manager)
with tabs[3]:
render_content_series_generator(
self.ai_generator,
self.content_generator,
self.seo_optimizer
)
with tabs[4]:
st.header("Analytics")
st.markdown("### Performance Insights")
selected_content = st.selectbox(
"Select content to analyze",
options=[item.title for item in self.calendar_manager.get_calendar().get_all_content()],
key="analytics_content_select"
)
if selected_content:
content_item = next(
item for item in self.calendar_manager.get_calendar().get_all_content()
if item.title == selected_content
)
render_performance_insights(content_item, self.platform_adapter)
st.markdown("### Optimization History")
if selected_content in st.session_state.optimization_history:
st.json(st.session_state.optimization_history[selected_content])
self.logger.info("Dashboard render completed successfully (tabbed UI)")
except Exception as e:
self.logger.error(f"Error rendering dashboard: {str(e)}", exc_info=True)
st.error(f"An error occurred: {str(e)}")
def _inject_custom_css(self):
st.markdown("""
<style>
/* Add your custom CSS here if needed */
</style>
""", unsafe_allow_html=True)
def _get_calendar_data(self):
self.logger.info("_get_calendar_data called")
try:
calendar_obj = self.calendar_manager.get_calendar()
if not calendar_obj:
self.logger.info("No calendar found in manager")
return None
data = []
for date_str, items in calendar_obj.schedule.items():
for item in items:
data.append({
'date': pd.to_datetime(date_str),
'title': item.title,
'platform': item.platforms[0] if item.platforms else 'Unknown',
'type': item.content_type,
'status': item.status
})
df = pd.DataFrame(data) if data else None
return df
except Exception as e:
self.logger.error(f"Error loading calendar data: {str(e)}", exc_info=True)
st.error(f"Error loading calendar data: {str(e)}")
return None
def _add_content(self, content):
calendar = self.calendar_manager.get_calendar()
if not calendar:
st.error("No calendar found. Please create a calendar first.")
return
platform_map = {
'Blog': Platform.WEBSITE,
'Instagram': Platform.INSTAGRAM,
'Twitter': Platform.TWITTER,
'LinkedIn': Platform.LINKEDIN,
'Facebook': Platform.FACEBOOK,
}
platform_enum = platform_map.get(content['platform'], Platform.WEBSITE)
content_type_map = {
'Article': ContentType.BLOG_POST,
'Social Post': ContentType.SOCIAL_MEDIA,
'Video': ContentType.VIDEO,
'Newsletter': ContentType.NEWSLETTER,
}
content_type_enum = content_type_map.get(content['type'], ContentType.BLOG_POST)
seo_data = SEOData(
title=content['title'],
meta_description="",
keywords=[],
structured_data={},
)
new_item = ContentItem(
title=content['title'],
description="",
content_type=content_type_enum,
platforms=[platform_enum],
publish_date=pd.to_datetime(content['publish_date']),
seo_data=seo_data,
status=content.get('status', 'Draft')
)
calendar.add_content(new_item)
self.calendar_manager.save_calendar_to_json()
def _delete_content(self, row):
calendar = self.calendar_manager.get_calendar()
if not calendar:
return
for date_str, items in list(calendar.schedule.items()):
calendar.schedule[date_str] = [
item for item in items
if not (
item.title == row['title'] and
str(item.publish_date.date()) == str(row['date'].date()) and
item.platforms[0].name == str(row['platform']) and
item.content_type.name == str(row['type'])
)
]
if not calendar.schedule[date_str]:
del calendar.schedule[date_str]
self.calendar_manager.save_calendar_to_json()
def _edit_content(self, row, new_title, new_platform, new_type, new_status):
self._delete_content(row)
self._add_content({
'title': new_title,
'platform': new_platform,
'type': new_type,
'publish_date': row['date'],
'status': new_status
})
def _get_item_key(self, row):
key_str = f"{row['title']}_{row['date']}_{row['platform']}_{row['type']}"
return hashlib.md5(key_str.encode()).hexdigest()
def _generate_ai_suggestions(self, content_type, topic, audience, goals, tone, length, model_settings, style_preferences, seo_preferences, platform_settings):
"""Generate AI content suggestions based on input parameters."""
try:
self.logger.info(f"Generating AI suggestions for topic: {topic}")
# Map content type string to ContentType enum
content_type_map = {
'Blog Post': ContentType.BLOG_POST,
'Social Media Post': ContentType.SOCIAL_MEDIA,
'Video': ContentType.VIDEO,
'Newsletter': ContentType.NEWSLETTER,
'Article': ContentType.BLOG_POST,
'Social Post': ContentType.SOCIAL_MEDIA
}
content_type_enum = content_type_map.get(content_type, ContentType.BLOG_POST)
# Map platform string to Platform enum
platform_map = {
'Blog': Platform.WEBSITE,
'Instagram': Platform.INSTAGRAM,
'Twitter': Platform.TWITTER,
'LinkedIn': Platform.LINKEDIN,
'Facebook': Platform.FACEBOOK,
'Website': Platform.WEBSITE
}
platform = st.session_state.get('ai_modal_platform', 'Blog')
platform_enum = platform_map.get(platform, Platform.WEBSITE)
# Create a content item for the suggestion
content_item = ContentItem(
title=topic,
description="",
content_type=content_type_enum,
platforms=[platform_enum],
publish_date=datetime.now(),
seo_data=SEOData(
title=topic,
meta_description="",
keywords=[],
structured_data={}
),
status='Draft'
)
# Use AIGenerator to generate suggestions
suggestions = self.ai_generator.generate_ai_suggestions(
content_type=content_type_enum,
topic=topic,
audience=audience,
goals=goals,
tone=tone,
length=length,
model_settings=model_settings,
style_preferences=style_preferences,
seo_preferences=seo_preferences,
platform_settings=platform_settings,
platform=platform_enum
)
if not suggestions:
self.logger.warning("No suggestions generated")
return []
# Format suggestions
formatted_suggestions = []
for suggestion in suggestions:
formatted_suggestion = {
'title': suggestion.get('title', topic),
'type': content_type,
'platform': platform,
'audience': audience,
'impact': f"High impact for {', '.join(goals)}",
'preview': suggestion.get('preview', ''),
'style_elements': [
f"Tone: {tone}",
f"Length: {length}",
f"Creativity: {model_settings.get('Creativity Level', 'balanced')}",
f"Formality: {model_settings.get('Formality Level', 'professional')}"
],
'seo_elements': [
f"Keyword Density: {seo_preferences.get('Keyword Density', '2')}%",
"Internal Linking: Enabled" if seo_preferences.get('Internal Linking', True) else "Internal Linking: Disabled",
"External Linking: Enabled" if seo_preferences.get('External Linking', True) else "External Linking: Disabled"
],
'engagement_score': f"{85 + len(formatted_suggestions)*5}%",
'reach': 'High',
'conversion': f"{3.5 + len(formatted_suggestions)*0.5}%",
'seo_impact': 'Strong',
'platform_optimizations': suggestion.get('platform_optimizations', []),
'variations': suggestion.get('variations', [
"Alternative headline",
"Different content angle",
"Alternative format"
]),
'seo_recommendations': suggestion.get('seo_elements', []),
'media_suggestions': suggestion.get('media_suggestions', [
"Featured image",
"Supporting graphics",
"Social media visuals"
])
}
formatted_suggestions.append(formatted_suggestion)
self.logger.info(f"Generated {len(formatted_suggestions)} suggestions successfully")
return formatted_suggestions
except Exception as e:
self.logger.error(f"Error generating AI suggestions: {str(e)}", exc_info=True)
st.error(f"Error generating suggestions: {str(e)}")
return []
def _create_content_brief(self, content_item: ContentItem) -> Dict[str, Any]:
"""Create a detailed content brief for the given content item."""
try:
self.logger.info(f"Creating content brief for: {content_item.title}")
# Generate content brief using the content brief generator
brief = self.content_brief_generator.generate_brief(
content_item=content_item,
target_audience={
'audience': content_item.description,
'goals': ['engage', 'inform', 'convert']
}
)
# Enhance brief with SEO data
if brief and 'content_flow' in brief:
brief['seo_optimization'] = {
'meta_description': self.seo_optimizer.generate_meta_description(
brief['content_flow'].get('introduction', {}).get('summary', '')
),
'keywords': self.seo_optimizer.extract_keywords(
brief['content_flow'].get('introduction', {}).get('summary', '')
),
'structured_data': self.seo_optimizer.generate_structured_data(
content_item.content_type
)
}
self.logger.info(f"Content brief created successfully for: {content_item.title}")
return brief
except Exception as e:
self.logger.error(f"Error creating content brief: {str(e)}", exc_info=True)
st.error(f"Error creating content brief: {str(e)}")
return {}
def _schedule_content(self, content_item: ContentItem, publish_date: datetime) -> bool:
"""Schedule content for publishing on the specified date."""
try:
self.logger.info(f"Scheduling content: {content_item.title} for {publish_date}")
# Get the calendar
calendar = self.calendar_manager.get_calendar()
if not calendar:
raise ValueError("No calendar found")
# Update the publish date
content_item.publish_date = publish_date
# Add to calendar
calendar.add_content(content_item)
# Save changes
self.calendar_manager.save_calendar_to_json()
self.logger.info(f"Content scheduled successfully: {content_item.title}")
return True
except Exception as e:
self.logger.error(f"Error scheduling content: {str(e)}", exc_info=True)
st.error(f"Error scheduling content: {str(e)}")
return False
def _refine_suggestion(self, suggestion: Dict[str, Any], feedback: Dict[str, Any]) -> Dict[str, Any]:
"""Refine an AI-generated suggestion based on user feedback."""
try:
self.logger.info("Refining AI suggestion based on feedback")
# Update suggestion based on feedback
if 'tone' in feedback:
suggestion['style_elements'] = [
f"Tone: {feedback['tone']}",
*[elem for elem in suggestion['style_elements'] if not elem.startswith('Tone:')]
]
if 'length' in feedback:
suggestion['style_elements'] = [
f"Length: {feedback['length']}",
*[elem for elem in suggestion['style_elements'] if not elem.startswith('Length:')]
]
if 'keywords' in feedback:
suggestion['seo_elements'] = [
f"Keywords: {', '.join(feedback['keywords'])}",
*[elem for elem in suggestion['seo_elements'] if not elem.startswith('Keywords:')]
]
# Regenerate content with refined parameters
refined_content = self.content_brief_generator.generate_brief(
content_item=ContentItem(
title=suggestion['title'],
description="",
content_type=ContentType[suggestion['type'].upper().replace(' ', '_')],
platforms=[Platform[suggestion['platform'].upper()]],
publish_date=datetime.now(),
seo_data=SEOData(
title=suggestion['title'],
meta_description="",
keywords=feedback.get('keywords', []),
structured_data={}
),
status='Draft'
),
target_audience={
'audience': suggestion['audience'],
'goals': feedback.get('goals', ['engage', 'inform']),
'preferences': {
'tone': feedback.get('tone', 'professional'),
'length': feedback.get('length', 'medium')
}
}
)
if refined_content:
suggestion['preview'] = refined_content.get('content_flow', {}).get('introduction', {}).get('summary', '')
self.logger.info("Suggestion refined successfully")
return suggestion
except Exception as e:
self.logger.error(f"Error refining suggestion: {str(e)}", exc_info=True)
st.error(f"Error refining suggestion: {str(e)}")
return suggestion
def _customize_suggestion(self, suggestion: Dict[str, Any], customizations: Dict[str, Any]) -> Dict[str, Any]:
"""Customize an AI-generated suggestion with specific requirements."""
try:
self.logger.info("Customizing AI suggestion")
# Apply customizations
if 'title' in customizations:
suggestion['title'] = customizations['title']
if 'platform' in customizations:
suggestion['platform'] = customizations['platform']
if 'style' in customizations:
suggestion['style_elements'] = [
f"Tone: {customizations['style'].get('tone', 'professional')}",
f"Length: {customizations['style'].get('length', 'medium')}",
f"Creativity: {customizations['style'].get('creativity', 'balanced')}",
f"Formality: {customizations['style'].get('formality', 'professional')}"
]
if 'seo' in customizations:
suggestion['seo_elements'] = [
f"Keyword Density: {customizations['seo'].get('keyword_density', '2')}%",
"Internal Linking: Enabled" if customizations['seo'].get('internal_linking', True) else "Internal Linking: Disabled",
"External Linking: Enabled" if customizations['seo'].get('external_linking', True) else "External Linking: Disabled"
]
# Regenerate content with customizations
customized_content = self.content_brief_generator.generate_brief(
content_item=ContentItem(
title=suggestion['title'],
description="",
content_type=ContentType[suggestion['type'].upper().replace(' ', '_')],
platforms=[Platform[suggestion['platform'].upper()]],
publish_date=datetime.now(),
seo_data=SEOData(
title=suggestion['title'],
meta_description="",
keywords=customizations.get('seo', {}).get('keywords', []),
structured_data={}
),
status='Draft'
),
target_audience={
'audience': suggestion['audience'],
'goals': customizations.get('goals', ['engage', 'inform']),
'preferences': customizations.get('style', {})
}
)
if customized_content:
suggestion['preview'] = customized_content.get('content_flow', {}).get('introduction', {}).get('summary', '')
self.logger.info("Suggestion customized successfully")
return suggestion
except Exception as e:
self.logger.error(f"Error customizing suggestion: {str(e)}", exc_info=True)
st.error(f"Error customizing suggestion: {str(e)}")
return suggestion
def _optimize_content_for_platform(self, content_item: ContentItem, platform: Platform) -> Dict[str, Any]:
"""Optimize content specifically for a target platform."""
try:
self.logger.info(f"Optimizing content for {platform.name}: {content_item.title}")
# Get platform-specific requirements
platform_requirements = self.platform_adapter.get_platform_requirements(platform)
# Generate platform-optimized content
optimized_content = self.content_generator.optimize_for_platform(
content=content_item,
platform=platform,
requirements=platform_requirements
)
if not optimized_content:
raise ValueError(f"Failed to optimize content for {platform.name}")
# Enhance with AI
ai_enhanced = self.ai_generator.enhance_for_platform(
content=optimized_content,
platform=platform,
enhancement_type='platform_specific'
)
if ai_enhanced:
optimized_content.update(ai_enhanced)
# Track optimization history
if content_item.title not in st.session_state.optimization_history:
st.session_state.optimization_history[content_item.title] = []
st.session_state.optimization_history[content_item.title].append({
'platform': platform.name,
'timestamp': datetime.now(),
'changes': optimized_content.get('changes', [])
})
self.logger.info(f"Content optimized successfully for {platform.name}")
return optimized_content
except Exception as e:
self.logger.error(f"Error optimizing content: {str(e)}", exc_info=True)
st.error(f"Error optimizing content: {str(e)}")
return {}
if __name__ == "__main__":
dashboard = ContentCalendarDashboard()
dashboard.render()

View File

@@ -0,0 +1,30 @@
import streamlit as st
from datetime import datetime, timedelta
def render_filters():
with st.expander("Filters", expanded=False):
col1, col2 = st.columns(2)
with col1:
start_date = st.date_input("Start Date", st.session_state.get('filter_start_date', datetime.now()))
end_date = st.date_input("End Date", st.session_state.get('filter_end_date', datetime.now() + timedelta(days=30)))
st.session_state['filter_start_date'] = start_date
st.session_state['filter_end_date'] = end_date
with col2:
platforms = st.multiselect(
"Platforms",
["Blog", "Instagram", "Twitter", "LinkedIn", "Facebook"],
default=st.session_state.get('filter_platforms', ["Blog"])
)
st.session_state['filter_platforms'] = platforms
content_types = st.multiselect(
"Content Types",
["Article", "Social Post", "Video", "Newsletter"],
default=st.session_state.get('filter_content_types', ["Article"])
)
st.session_state['filter_content_types'] = content_types
statuses = st.multiselect(
"Status",
["Draft", "Scheduled", "Published", "Archived"],
default=st.session_state.get('filter_statuses', ["Draft", "Scheduled"])
)
st.session_state['filter_statuses'] = statuses

View File

@@ -0,0 +1,198 @@
from datetime import datetime, timedelta
from typing import Dict, List, Any
import calendar
import random
def calculate_publish_dates(
topics: List[Dict[str, Any]],
start_date: datetime,
duration: str
) -> Dict[str, List[Dict[str, Any]]]:
"""
Calculate optimal publish dates for content topics.
Args:
topics: List of content topics to schedule
start_date: When to start publishing
duration: How long the calendar should span ('weekly', 'monthly', 'quarterly')
Returns:
Dictionary mapping dates to scheduled content
"""
# Calculate end date based on duration
end_date = _calculate_end_date(start_date, duration)
# Get all dates in range
dates = _get_dates_in_range(start_date, end_date)
# Calculate optimal posting frequency
frequency = _calculate_posting_frequency(len(topics), len(dates))
# Schedule content
schedule = _schedule_content(topics, dates, frequency)
return schedule
def _calculate_end_date(start_date: datetime, duration: str) -> datetime:
"""Calculate end date based on duration."""
if duration == 'weekly':
return start_date + timedelta(days=7)
elif duration == 'monthly':
# Add one month
if start_date.month == 12:
return datetime(start_date.year + 1, 1, start_date.day)
return datetime(start_date.year, start_date.month + 1, start_date.day)
elif duration == 'quarterly':
# Add three months
new_month = start_date.month + 3
new_year = start_date.year
if new_month > 12:
new_month -= 12
new_year += 1
return datetime(new_year, new_month, start_date.day)
else:
raise ValueError(f"Invalid duration: {duration}")
def _get_dates_in_range(
start_date: datetime,
end_date: datetime
) -> List[datetime]:
"""Get all dates in the given range."""
dates = []
current_date = start_date
while current_date <= end_date:
# Skip weekends
if current_date.weekday() < 5: # 0-4 are weekdays
dates.append(current_date)
current_date += timedelta(days=1)
return dates
def _calculate_posting_frequency(
num_topics: int,
num_dates: int
) -> Dict[str, int]:
"""
Calculate optimal posting frequency based on number of topics and dates.
Returns:
Dictionary with posting frequency for each content type
"""
# Calculate base frequency
base_frequency = num_dates / num_topics
# Adjust for content types
return {
'blog_post': max(1, int(base_frequency * 0.4)), # 40% of content
'social_media': max(1, int(base_frequency * 0.3)), # 30% of content
'video': max(1, int(base_frequency * 0.2)), # 20% of content
'newsletter': max(1, int(base_frequency * 0.1)) # 10% of content
}
def _schedule_content(
topics: List[Dict[str, Any]],
dates: List[datetime],
frequency: Dict[str, int]
) -> Dict[str, List[Dict[str, Any]]]:
"""
Schedule content topics across available dates.
Args:
topics: List of content topics to schedule
dates: Available dates for scheduling
frequency: Posting frequency for each content type
Returns:
Dictionary mapping dates to scheduled content
"""
schedule = {}
current_date_index = 0
# Group topics by content type
topics_by_type = _group_topics_by_type(topics)
# Schedule each content type
for content_type, type_topics in topics_by_type.items():
type_frequency = frequency.get(content_type, 1)
for topic in type_topics:
# Find next available date
while current_date_index < len(dates):
date = dates[current_date_index]
date_str = date.strftime('%Y-%m-%d')
# Check if date is available
if date_str not in schedule:
schedule[date_str] = []
# Add topic to schedule
schedule[date_str].append(topic)
# Move to next date based on frequency
current_date_index += type_frequency
break
# If we've used all dates, wrap around
if current_date_index >= len(dates):
current_date_index = 0
return schedule
def _group_topics_by_type(
topics: List[Dict[str, Any]]
) -> Dict[str, List[Dict[str, Any]]]:
"""Group topics by their content type."""
grouped = {}
for topic in topics:
content_type = topic.get('content_type', 'blog_post')
if content_type not in grouped:
grouped[content_type] = []
grouped[content_type].append(topic)
return grouped
def get_optimal_posting_time(
content_type: str,
platform: str
) -> datetime.time:
"""
Get optimal posting time for content type and platform.
Args:
content_type: Type of content
platform: Target platform
Returns:
Optimal time to post
"""
# Default optimal times (can be customized based on platform analytics)
optimal_times = {
'blog_post': {
'website': datetime.time(9, 0), # 9 AM
'medium': datetime.time(10, 0) # 10 AM
},
'social_media': {
'facebook': datetime.time(15, 0), # 3 PM
'twitter': datetime.time(12, 0), # 12 PM
'linkedin': datetime.time(8, 0), # 8 AM
'instagram': datetime.time(19, 0) # 7 PM
},
'video': {
'youtube': datetime.time(14, 0) # 2 PM
},
'newsletter': {
'email': datetime.time(6, 0) # 6 AM
}
}
# Get optimal time for content type and platform
content_times = optimal_times.get(content_type, {})
optimal_time = content_times.get(platform)
if optimal_time is None:
# Default to 9 AM if no specific time is set
optimal_time = datetime.time(9, 0)
return optimal_time

View File

@@ -0,0 +1,154 @@
import functools
import logging
from typing import Any, Callable, TypeVar, cast
from datetime import datetime
logger = logging.getLogger(__name__)
T = TypeVar('T')
def handle_calendar_error(func: Callable[..., T]) -> Callable[..., T]:
"""
Decorator to handle errors in calendar operations.
Args:
func: Function to decorate
Returns:
Decorated function with error handling
"""
@functools.wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> T:
try:
return func(*args, **kwargs)
except ValueError as e:
logger.error(f"Invalid input in {func.__name__}: {str(e)}")
raise
except Exception as e:
logger.error(f"Error in {func.__name__}: {str(e)}")
raise CalendarError(f"Calendar operation failed: {str(e)}")
return cast(Callable[..., T], wrapper)
class CalendarError(Exception):
"""Base exception for calendar-related errors."""
pass
class ContentError(CalendarError):
"""Exception for content-related errors."""
pass
class SchedulingError(CalendarError):
"""Exception for scheduling-related errors."""
pass
class ValidationError(CalendarError):
"""Exception for validation-related errors."""
pass
def validate_date_range(
start_date: datetime,
end_date: datetime
) -> None:
"""
Validate date range for calendar operations.
Args:
start_date: Start date
end_date: End date
Raises:
ValidationError: If date range is invalid
"""
if not isinstance(start_date, datetime):
raise ValidationError("Start date must be a datetime object")
if not isinstance(end_date, datetime):
raise ValidationError("End date must be a datetime object")
if start_date > end_date:
raise ValidationError("Start date must be before end date")
if (end_date - start_date).days > 365:
raise ValidationError("Calendar duration cannot exceed one year")
def validate_content_item(content: dict) -> None:
"""
Validate content item structure.
Args:
content: Content item to validate
Raises:
ValidationError: If content item is invalid
"""
required_fields = ['title', 'description', 'content_type', 'platforms']
for field in required_fields:
if field not in content:
raise ValidationError(f"Missing required field: {field}")
if not isinstance(content['platforms'], list):
raise ValidationError("Platforms must be a list")
if not content['platforms']:
raise ValidationError("At least one platform must be specified")
def validate_calendar_duration(duration: str) -> None:
"""
Validate calendar duration.
Args:
duration: Duration to validate ('weekly', 'monthly', 'quarterly')
Raises:
ValidationError: If duration is invalid
"""
valid_durations = ['weekly', 'monthly', 'quarterly']
if duration not in valid_durations:
raise ValidationError(
f"Invalid duration: {duration}. "
f"Must be one of: {', '.join(valid_durations)}"
)
def log_calendar_operation(
operation: str,
details: dict
) -> None:
"""
Log calendar operation details.
Args:
operation: Name of the operation
details: Operation details to log
"""
logger.info(f"Calendar operation: {operation}")
logger.debug(f"Operation details: {details}")
def handle_api_error(
error: Exception,
operation: str
) -> None:
"""
Handle API-related errors.
Args:
error: The error that occurred
operation: The operation that failed
"""
logger.error(f"API error in {operation}: {str(error)}")
raise CalendarError(f"API operation failed: {str(error)}")
def handle_integration_error(
error: Exception,
integration: str
) -> None:
"""
Handle integration-related errors.
Args:
error: The error that occurred
integration: The integration that failed
"""
logger.error(f"Integration error with {integration}: {str(error)}")
raise CalendarError(f"Integration failed: {str(error)}")