ALwrity Version 0.5.0 (Fastapi + React )
This commit is contained in:
1
backend/models/__init__.py
Normal file
1
backend/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Models package for Alwrity
|
||||
260
backend/models/component_logic.py
Normal file
260
backend/models/component_logic.py
Normal file
@@ -0,0 +1,260 @@
|
||||
"""Pydantic models for component logic requests and responses."""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from pydantic import BaseModel, EmailStr, validator
|
||||
import re
|
||||
|
||||
# AI Research Models
|
||||
|
||||
class UserInfoRequest(BaseModel):
|
||||
"""Request model for user information validation."""
|
||||
full_name: str
|
||||
email: str
|
||||
company: str
|
||||
role: str
|
||||
|
||||
@validator('full_name')
|
||||
def validate_full_name(cls, v):
|
||||
if not v or len(v.strip()) < 2:
|
||||
raise ValueError('Full name must be at least 2 characters long')
|
||||
return v.strip()
|
||||
|
||||
@validator('email')
|
||||
def validate_email(cls, v):
|
||||
# Basic email validation
|
||||
email_pattern = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
|
||||
if not email_pattern.match(v):
|
||||
raise ValueError('Invalid email format')
|
||||
return v.lower()
|
||||
|
||||
@validator('company')
|
||||
def validate_company(cls, v):
|
||||
if not v or len(v.strip()) < 1:
|
||||
raise ValueError('Company name is required')
|
||||
return v.strip()
|
||||
|
||||
@validator('role')
|
||||
def validate_role(cls, v):
|
||||
valid_roles = ["Content Creator", "Marketing Manager", "Business Owner", "Other"]
|
||||
if v not in valid_roles:
|
||||
raise ValueError(f'Role must be one of: {", ".join(valid_roles)}')
|
||||
return v
|
||||
|
||||
class ResearchPreferencesRequest(BaseModel):
|
||||
"""Request model for research preferences configuration."""
|
||||
research_depth: str
|
||||
content_types: List[str]
|
||||
auto_research: bool
|
||||
factual_content: bool = True # Default to True
|
||||
|
||||
@validator('research_depth')
|
||||
def validate_research_depth(cls, v):
|
||||
valid_depths = ["Basic", "Standard", "Deep", "Comprehensive"]
|
||||
if v not in valid_depths:
|
||||
raise ValueError(f'Research depth must be one of: {", ".join(valid_depths)}')
|
||||
return v
|
||||
|
||||
@validator('content_types')
|
||||
def validate_content_types(cls, v):
|
||||
valid_types = ["Blog Posts", "Social Media", "Technical Articles", "News", "Academic Papers"]
|
||||
if not v:
|
||||
raise ValueError('At least one content type must be selected')
|
||||
for content_type in v:
|
||||
if content_type not in valid_types:
|
||||
raise ValueError(f'Invalid content type: {content_type}')
|
||||
return v
|
||||
|
||||
class ResearchRequest(BaseModel):
|
||||
"""Request model for research processing."""
|
||||
topic: str
|
||||
preferences: ResearchPreferencesRequest
|
||||
|
||||
@validator('topic')
|
||||
def validate_topic(cls, v):
|
||||
if not v or len(v.strip()) < 3:
|
||||
raise ValueError('Topic must be at least 3 characters long')
|
||||
return v.strip()
|
||||
|
||||
class UserInfoResponse(BaseModel):
|
||||
"""Response model for user information validation."""
|
||||
valid: bool
|
||||
user_info: Optional[Dict[str, Any]] = None
|
||||
errors: List[str] = []
|
||||
|
||||
class ResearchPreferencesResponse(BaseModel):
|
||||
"""Response model for research preferences configuration."""
|
||||
valid: bool
|
||||
preferences: Optional[Dict[str, Any]] = None
|
||||
errors: List[str] = []
|
||||
|
||||
class ResearchResponse(BaseModel):
|
||||
"""Response model for research processing."""
|
||||
success: bool
|
||||
topic: str
|
||||
results: Optional[Dict[str, Any]] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
# Personalization Models
|
||||
|
||||
class ContentStyleRequest(BaseModel):
|
||||
"""Request model for content style configuration."""
|
||||
writing_style: str
|
||||
tone: str
|
||||
content_length: str
|
||||
|
||||
@validator('writing_style')
|
||||
def validate_writing_style(cls, v):
|
||||
valid_styles = ["Professional", "Casual", "Technical", "Conversational", "Academic"]
|
||||
if v not in valid_styles:
|
||||
raise ValueError(f'Writing style must be one of: {", ".join(valid_styles)}')
|
||||
return v
|
||||
|
||||
@validator('tone')
|
||||
def validate_tone(cls, v):
|
||||
valid_tones = ["Formal", "Semi-Formal", "Neutral", "Friendly", "Humorous"]
|
||||
if v not in valid_tones:
|
||||
raise ValueError(f'Tone must be one of: {", ".join(valid_tones)}')
|
||||
return v
|
||||
|
||||
@validator('content_length')
|
||||
def validate_content_length(cls, v):
|
||||
valid_lengths = ["Concise", "Standard", "Detailed", "Comprehensive"]
|
||||
if v not in valid_lengths:
|
||||
raise ValueError(f'Content length must be one of: {", ".join(valid_lengths)}')
|
||||
return v
|
||||
|
||||
class BrandVoiceRequest(BaseModel):
|
||||
"""Request model for brand voice configuration."""
|
||||
personality_traits: List[str]
|
||||
voice_description: Optional[str] = None
|
||||
keywords: Optional[str] = None
|
||||
|
||||
@validator('personality_traits')
|
||||
def validate_personality_traits(cls, v):
|
||||
valid_traits = ["Professional", "Innovative", "Friendly", "Trustworthy", "Creative", "Expert"]
|
||||
if not v:
|
||||
raise ValueError('At least one personality trait must be selected')
|
||||
for trait in v:
|
||||
if trait not in valid_traits:
|
||||
raise ValueError(f'Invalid personality trait: {trait}')
|
||||
return v
|
||||
|
||||
@validator('voice_description')
|
||||
def validate_voice_description(cls, v):
|
||||
if v and len(v.strip()) < 10:
|
||||
raise ValueError('Voice description must be at least 10 characters long')
|
||||
return v.strip() if v else None
|
||||
|
||||
class AdvancedSettingsRequest(BaseModel):
|
||||
"""Request model for advanced content generation settings."""
|
||||
seo_optimization: bool
|
||||
readability_level: str
|
||||
content_structure: List[str]
|
||||
|
||||
@validator('readability_level')
|
||||
def validate_readability_level(cls, v):
|
||||
valid_levels = ["Simple", "Standard", "Advanced", "Expert"]
|
||||
if v not in valid_levels:
|
||||
raise ValueError(f'Readability level must be one of: {", ".join(valid_levels)}')
|
||||
return v
|
||||
|
||||
@validator('content_structure')
|
||||
def validate_content_structure(cls, v):
|
||||
valid_structures = ["Introduction", "Key Points", "Examples", "Conclusion", "Call-to-Action"]
|
||||
if not v:
|
||||
raise ValueError('At least one content structure element must be selected')
|
||||
for structure in v:
|
||||
if structure not in valid_structures:
|
||||
raise ValueError(f'Invalid content structure: {structure}')
|
||||
return v
|
||||
|
||||
class PersonalizationSettingsRequest(BaseModel):
|
||||
"""Request model for complete personalization settings."""
|
||||
content_style: ContentStyleRequest
|
||||
brand_voice: BrandVoiceRequest
|
||||
advanced_settings: AdvancedSettingsRequest
|
||||
|
||||
class ContentStyleResponse(BaseModel):
|
||||
"""Response model for content style validation."""
|
||||
valid: bool
|
||||
style_config: Optional[Dict[str, Any]] = None
|
||||
errors: List[str] = []
|
||||
|
||||
class BrandVoiceResponse(BaseModel):
|
||||
"""Response model for brand voice configuration."""
|
||||
valid: bool
|
||||
brand_config: Optional[Dict[str, Any]] = None
|
||||
errors: List[str] = []
|
||||
|
||||
class PersonalizationSettingsResponse(BaseModel):
|
||||
"""Response model for complete personalization settings."""
|
||||
valid: bool
|
||||
settings: Optional[Dict[str, Any]] = None
|
||||
errors: List[str] = []
|
||||
|
||||
# Research Utilities Models
|
||||
|
||||
class ResearchTopicRequest(BaseModel):
|
||||
"""Request model for topic research."""
|
||||
topic: str
|
||||
api_keys: Dict[str, str]
|
||||
|
||||
@validator('topic')
|
||||
def validate_topic(cls, v):
|
||||
if not v or len(v.strip()) < 3:
|
||||
raise ValueError('Topic must be at least 3 characters long')
|
||||
return v.strip()
|
||||
|
||||
class ResearchResultResponse(BaseModel):
|
||||
"""Response model for research results."""
|
||||
success: bool
|
||||
topic: str
|
||||
data: Optional[Dict[str, Any]] = None
|
||||
error: Optional[str] = None
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
|
||||
# Style Detection Models
|
||||
class StyleAnalysisRequest(BaseModel):
|
||||
"""Request model for style analysis."""
|
||||
content: Dict[str, Any]
|
||||
analysis_type: str = "comprehensive" # comprehensive, patterns, guidelines
|
||||
|
||||
class StyleAnalysisResponse(BaseModel):
|
||||
"""Response model for style analysis."""
|
||||
success: bool
|
||||
analysis: Optional[Dict[str, Any]] = None
|
||||
patterns: Optional[Dict[str, Any]] = None
|
||||
guidelines: Optional[Dict[str, Any]] = None
|
||||
error: Optional[str] = None
|
||||
timestamp: str
|
||||
|
||||
class WebCrawlRequest(BaseModel):
|
||||
"""Request model for web crawling."""
|
||||
url: Optional[str] = None
|
||||
text_sample: Optional[str] = None
|
||||
|
||||
class WebCrawlResponse(BaseModel):
|
||||
"""Response model for web crawling."""
|
||||
success: bool
|
||||
content: Optional[Dict[str, Any]] = None
|
||||
metrics: Optional[Dict[str, Any]] = None
|
||||
error: Optional[str] = None
|
||||
timestamp: str
|
||||
|
||||
class StyleDetectionRequest(BaseModel):
|
||||
"""Request model for complete style detection workflow."""
|
||||
url: Optional[str] = None
|
||||
text_sample: Optional[str] = None
|
||||
include_patterns: bool = True
|
||||
include_guidelines: bool = True
|
||||
|
||||
class StyleDetectionResponse(BaseModel):
|
||||
"""Response model for complete style detection workflow."""
|
||||
success: bool
|
||||
crawl_result: Optional[Dict[str, Any]] = None
|
||||
style_analysis: Optional[Dict[str, Any]] = None
|
||||
style_patterns: Optional[Dict[str, Any]] = None
|
||||
style_guidelines: Optional[Dict[str, Any]] = None
|
||||
error: Optional[str] = None
|
||||
warning: Optional[str] = None
|
||||
timestamp: str
|
||||
239
backend/models/content_planning.py
Normal file
239
backend/models/content_planning.py
Normal file
@@ -0,0 +1,239 @@
|
||||
"""
|
||||
Content Planning Database Models
|
||||
Defines the database schema for content strategy, calendar events, and analytics.
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, Float, JSON, ForeignKey
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class ContentStrategy(Base):
|
||||
"""Content Strategy model."""
|
||||
|
||||
__tablename__ = "content_strategies"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, nullable=False)
|
||||
name = Column(String(255), nullable=False)
|
||||
industry = Column(String(100), nullable=True)
|
||||
target_audience = Column(JSON, nullable=True) # Store audience demographics and preferences
|
||||
content_pillars = Column(JSON, nullable=True) # Store content pillar definitions
|
||||
ai_recommendations = Column(JSON, nullable=True) # Store AI-generated recommendations
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
calendar_events = relationship("CalendarEvent", back_populates="strategy")
|
||||
analytics = relationship("ContentAnalytics", back_populates="strategy")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ContentStrategy(id={self.id}, name='{self.name}', industry='{self.industry}')>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert model to dictionary."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'user_id': self.user_id,
|
||||
'name': self.name,
|
||||
'industry': self.industry,
|
||||
'target_audience': self.target_audience,
|
||||
'content_pillars': self.content_pillars,
|
||||
'ai_recommendations': self.ai_recommendations,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
|
||||
class CalendarEvent(Base):
|
||||
"""Calendar Event model."""
|
||||
|
||||
__tablename__ = "calendar_events"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
strategy_id = Column(Integer, ForeignKey("content_strategies.id"), nullable=False)
|
||||
title = Column(String(255), nullable=False)
|
||||
description = Column(Text, nullable=True)
|
||||
content_type = Column(String(50), nullable=False) # blog_post, video, social_post, etc.
|
||||
platform = Column(String(50), nullable=False) # website, linkedin, youtube, etc.
|
||||
scheduled_date = Column(DateTime, nullable=False)
|
||||
status = Column(String(20), default="draft") # draft, scheduled, published, cancelled
|
||||
ai_recommendations = Column(JSON, nullable=True) # Store AI recommendations for the event
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
strategy = relationship("ContentStrategy", back_populates="calendar_events")
|
||||
analytics = relationship("ContentAnalytics", back_populates="event")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<CalendarEvent(id={self.id}, title='{self.title}', status='{self.status}')>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert model to dictionary."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'strategy_id': self.strategy_id,
|
||||
'title': self.title,
|
||||
'description': self.description,
|
||||
'content_type': self.content_type,
|
||||
'platform': self.platform,
|
||||
'scheduled_date': self.scheduled_date.isoformat() if self.scheduled_date else None,
|
||||
'status': self.status,
|
||||
'ai_recommendations': self.ai_recommendations,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
|
||||
class ContentAnalytics(Base):
|
||||
"""Content Analytics model."""
|
||||
|
||||
__tablename__ = "content_analytics"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
event_id = Column(Integer, ForeignKey("calendar_events.id"), nullable=True)
|
||||
strategy_id = Column(Integer, ForeignKey("content_strategies.id"), nullable=True)
|
||||
platform = Column(String(50), nullable=False) # website, linkedin, youtube, etc.
|
||||
metrics = Column(JSON, nullable=True) # Store various performance metrics
|
||||
performance_score = Column(Float, nullable=True) # Overall performance score
|
||||
recorded_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
event = relationship("CalendarEvent", back_populates="analytics")
|
||||
strategy = relationship("ContentStrategy", back_populates="analytics")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ContentAnalytics(id={self.id}, platform='{self.platform}', score={self.performance_score})>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert model to dictionary."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'event_id': self.event_id,
|
||||
'strategy_id': self.strategy_id,
|
||||
'platform': self.platform,
|
||||
'metrics': self.metrics,
|
||||
'performance_score': self.performance_score,
|
||||
'recorded_at': self.recorded_at.isoformat() if self.recorded_at else None
|
||||
}
|
||||
|
||||
class ContentGapAnalysis(Base):
|
||||
"""Content Gap Analysis model."""
|
||||
|
||||
__tablename__ = "content_gap_analyses"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, nullable=False)
|
||||
website_url = Column(String(500), nullable=False)
|
||||
competitor_urls = Column(JSON, nullable=True) # Store competitor URLs
|
||||
target_keywords = Column(JSON, nullable=True) # Store target keywords
|
||||
analysis_results = Column(JSON, nullable=True) # Store complete analysis results
|
||||
recommendations = Column(JSON, nullable=True) # Store AI recommendations
|
||||
opportunities = Column(JSON, nullable=True) # Store identified opportunities
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ContentGapAnalysis(id={self.id}, website='{self.website_url}')>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert model to dictionary."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'user_id': self.user_id,
|
||||
'website_url': self.website_url,
|
||||
'competitor_urls': self.competitor_urls,
|
||||
'target_keywords': self.target_keywords,
|
||||
'analysis_results': self.analysis_results,
|
||||
'recommendations': self.recommendations,
|
||||
'opportunities': self.opportunities,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
|
||||
class ContentRecommendation(Base):
|
||||
"""Content Recommendation model."""
|
||||
|
||||
__tablename__ = "content_recommendations"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
strategy_id = Column(Integer, ForeignKey("content_strategies.id"), nullable=True)
|
||||
user_id = Column(Integer, nullable=False)
|
||||
recommendation_type = Column(String(50), nullable=False) # blog_post, video, case_study, etc.
|
||||
title = Column(String(255), nullable=False)
|
||||
description = Column(Text, nullable=True)
|
||||
target_keywords = Column(JSON, nullable=True) # Store target keywords
|
||||
estimated_length = Column(String(100), nullable=True) # Estimated content length
|
||||
priority = Column(String(20), default="medium") # low, medium, high
|
||||
platforms = Column(JSON, nullable=True) # Store target platforms
|
||||
estimated_performance = Column(String(100), nullable=True) # Performance prediction
|
||||
status = Column(String(20), default="pending") # pending, accepted, rejected, implemented
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
strategy = relationship("ContentStrategy")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ContentRecommendation(id={self.id}, title='{self.title}', type='{self.recommendation_type}')>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert model to dictionary."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'strategy_id': self.strategy_id,
|
||||
'user_id': self.user_id,
|
||||
'recommendation_type': self.recommendation_type,
|
||||
'title': self.title,
|
||||
'description': self.description,
|
||||
'target_keywords': self.target_keywords,
|
||||
'estimated_length': self.estimated_length,
|
||||
'priority': self.priority,
|
||||
'platforms': self.platforms,
|
||||
'estimated_performance': self.estimated_performance,
|
||||
'status': self.status,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
|
||||
class AIAnalysisResult(Base):
|
||||
"""AI Analysis Result model for storing AI-generated insights and recommendations."""
|
||||
|
||||
__tablename__ = "ai_analysis_results"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, nullable=False)
|
||||
strategy_id = Column(Integer, ForeignKey("content_strategies.id"), nullable=True)
|
||||
analysis_type = Column(String(50), nullable=False) # performance_trends, strategic_intelligence, content_evolution, gap_analysis
|
||||
insights = Column(JSON, nullable=True) # Store AI-generated insights
|
||||
recommendations = Column(JSON, nullable=True) # Store AI-generated recommendations
|
||||
performance_metrics = Column(JSON, nullable=True) # Store performance data
|
||||
personalized_data_used = Column(JSON, nullable=True) # Store the onboarding data used for personalization
|
||||
processing_time = Column(Float, nullable=True) # Store processing time in seconds
|
||||
ai_service_status = Column(String(20), default="operational") # operational, fallback, error
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
strategy = relationship("ContentStrategy")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<AIAnalysisResult(id={self.id}, type='{self.analysis_type}', user_id={self.user_id})>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert model to dictionary."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'user_id': self.user_id,
|
||||
'strategy_id': self.strategy_id,
|
||||
'analysis_type': self.analysis_type,
|
||||
'insights': self.insights,
|
||||
'recommendations': self.recommendations,
|
||||
'performance_metrics': self.performance_metrics,
|
||||
'personalized_data_used': self.personalized_data_used,
|
||||
'processing_time': self.processing_time,
|
||||
'ai_service_status': self.ai_service_status,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
269
backend/models/enhanced_calendar_models.py
Normal file
269
backend/models/enhanced_calendar_models.py
Normal file
@@ -0,0 +1,269 @@
|
||||
"""
|
||||
Enhanced Calendar Models for AI-Powered Content Planning
|
||||
Defines additional database schema for intelligent calendar generation and optimization.
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, Float, JSON, ForeignKey, Boolean
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class ContentCalendarTemplate(Base):
|
||||
"""Template for industry-specific content calendars."""
|
||||
|
||||
__tablename__ = "content_calendar_templates"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
industry = Column(String(100), nullable=False)
|
||||
business_size = Column(String(50), nullable=True) # startup, sme, enterprise
|
||||
content_pillars = Column(JSON, nullable=True) # Core content themes
|
||||
posting_frequency = Column(JSON, nullable=True) # Platform-specific frequency
|
||||
platform_strategies = Column(JSON, nullable=True) # Platform-specific content types
|
||||
optimal_timing = Column(JSON, nullable=True) # Best posting times per platform
|
||||
content_mix = Column(JSON, nullable=True) # Content type distribution
|
||||
seasonal_themes = Column(JSON, nullable=True) # Seasonal content opportunities
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ContentCalendarTemplate(id={self.id}, industry='{self.industry}')>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'industry': self.industry,
|
||||
'business_size': self.business_size,
|
||||
'content_pillars': self.content_pillars,
|
||||
'posting_frequency': self.posting_frequency,
|
||||
'platform_strategies': self.platform_strategies,
|
||||
'optimal_timing': self.optimal_timing,
|
||||
'content_mix': self.content_mix,
|
||||
'seasonal_themes': self.seasonal_themes,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
|
||||
class AICalendarRecommendation(Base):
|
||||
"""AI-generated calendar recommendations and suggestions."""
|
||||
|
||||
__tablename__ = "ai_calendar_recommendations"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
strategy_id = Column(Integer, ForeignKey("content_strategies.id"), nullable=True)
|
||||
user_id = Column(Integer, nullable=False)
|
||||
recommendation_type = Column(String(50), nullable=False) # calendar_generation, content_optimization, performance_analysis
|
||||
content_suggestions = Column(JSON, nullable=True) # Suggested content topics and themes
|
||||
optimal_timing = Column(JSON, nullable=True) # Recommended posting times
|
||||
performance_prediction = Column(JSON, nullable=True) # Predicted performance metrics
|
||||
platform_recommendations = Column(JSON, nullable=True) # Platform-specific suggestions
|
||||
content_repurposing = Column(JSON, nullable=True) # Repurposing opportunities
|
||||
trending_topics = Column(JSON, nullable=True) # Trending topics to incorporate
|
||||
competitor_insights = Column(JSON, nullable=True) # Competitor analysis insights
|
||||
ai_confidence = Column(Float, nullable=True) # AI confidence score
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
strategy = relationship("ContentStrategy")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<AICalendarRecommendation(id={self.id}, type='{self.recommendation_type}')>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'strategy_id': self.strategy_id,
|
||||
'user_id': self.user_id,
|
||||
'recommendation_type': self.recommendation_type,
|
||||
'content_suggestions': self.content_suggestions,
|
||||
'optimal_timing': self.optimal_timing,
|
||||
'performance_prediction': self.performance_prediction,
|
||||
'platform_recommendations': self.platform_recommendations,
|
||||
'content_repurposing': self.content_repurposing,
|
||||
'trending_topics': self.trending_topics,
|
||||
'competitor_insights': self.competitor_insights,
|
||||
'ai_confidence': self.ai_confidence,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
|
||||
class ContentPerformanceTracking(Base):
|
||||
"""Detailed content performance tracking and analytics."""
|
||||
|
||||
__tablename__ = "content_performance_tracking"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
event_id = Column(Integer, ForeignKey("calendar_events.id"), nullable=True)
|
||||
strategy_id = Column(Integer, ForeignKey("content_strategies.id"), nullable=True)
|
||||
platform = Column(String(50), nullable=False) # website, linkedin, instagram, etc.
|
||||
content_type = Column(String(50), nullable=False) # blog_post, video, social_post, etc.
|
||||
metrics = Column(JSON, nullable=True) # Engagement, reach, clicks, conversions, etc.
|
||||
performance_score = Column(Float, nullable=True) # Overall performance score (0-100)
|
||||
audience_demographics = Column(JSON, nullable=True) # Audience insights
|
||||
engagement_rate = Column(Float, nullable=True) # Engagement rate percentage
|
||||
reach_count = Column(Integer, nullable=True) # Total reach
|
||||
click_count = Column(Integer, nullable=True) # Total clicks
|
||||
conversion_count = Column(Integer, nullable=True) # Total conversions
|
||||
roi = Column(Float, nullable=True) # Return on investment
|
||||
recorded_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
event = relationship("CalendarEvent")
|
||||
strategy = relationship("ContentStrategy")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ContentPerformanceTracking(id={self.id}, platform='{self.platform}', score={self.performance_score})>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'event_id': self.event_id,
|
||||
'strategy_id': self.strategy_id,
|
||||
'platform': self.platform,
|
||||
'content_type': self.content_type,
|
||||
'metrics': self.metrics,
|
||||
'performance_score': self.performance_score,
|
||||
'audience_demographics': self.audience_demographics,
|
||||
'engagement_rate': self.engagement_rate,
|
||||
'reach_count': self.reach_count,
|
||||
'click_count': self.click_count,
|
||||
'conversion_count': self.conversion_count,
|
||||
'roi': self.roi,
|
||||
'recorded_at': self.recorded_at.isoformat() if self.recorded_at else None
|
||||
}
|
||||
|
||||
class ContentTrendAnalysis(Base):
|
||||
"""Trend analysis and topic recommendations."""
|
||||
|
||||
__tablename__ = "content_trend_analysis"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, nullable=False)
|
||||
strategy_id = Column(Integer, ForeignKey("content_strategies.id"), nullable=True)
|
||||
industry = Column(String(100), nullable=False)
|
||||
trending_topics = Column(JSON, nullable=True) # Trending topics in the industry
|
||||
keyword_opportunities = Column(JSON, nullable=True) # High-value keywords
|
||||
content_gaps = Column(JSON, nullable=True) # Identified content gaps
|
||||
seasonal_opportunities = Column(JSON, nullable=True) # Seasonal content opportunities
|
||||
competitor_analysis = Column(JSON, nullable=True) # Competitor content analysis
|
||||
viral_potential = Column(JSON, nullable=True) # Content with viral potential
|
||||
audience_interests = Column(JSON, nullable=True) # Current audience interests
|
||||
analysis_date = Column(DateTime, default=datetime.utcnow)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
strategy = relationship("ContentStrategy")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ContentTrendAnalysis(id={self.id}, industry='{self.industry}')>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'user_id': self.user_id,
|
||||
'strategy_id': self.strategy_id,
|
||||
'industry': self.industry,
|
||||
'trending_topics': self.trending_topics,
|
||||
'keyword_opportunities': self.keyword_opportunities,
|
||||
'content_gaps': self.content_gaps,
|
||||
'seasonal_opportunities': self.seasonal_opportunities,
|
||||
'competitor_analysis': self.competitor_analysis,
|
||||
'viral_potential': self.viral_potential,
|
||||
'audience_interests': self.audience_interests,
|
||||
'analysis_date': self.analysis_date.isoformat() if self.analysis_date else None,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
|
||||
class ContentOptimization(Base):
|
||||
"""Content optimization recommendations and suggestions."""
|
||||
|
||||
__tablename__ = "content_optimizations"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
event_id = Column(Integer, ForeignKey("calendar_events.id"), nullable=True)
|
||||
user_id = Column(Integer, nullable=False)
|
||||
original_content = Column(JSON, nullable=True) # Original content details
|
||||
optimized_content = Column(JSON, nullable=True) # Optimized content suggestions
|
||||
platform_adaptations = Column(JSON, nullable=True) # Platform-specific adaptations
|
||||
visual_recommendations = Column(JSON, nullable=True) # Visual content suggestions
|
||||
hashtag_suggestions = Column(JSON, nullable=True) # Hashtag recommendations
|
||||
keyword_optimization = Column(JSON, nullable=True) # SEO keyword optimization
|
||||
tone_adjustments = Column(JSON, nullable=True) # Tone and style adjustments
|
||||
length_optimization = Column(JSON, nullable=True) # Content length optimization
|
||||
performance_prediction = Column(JSON, nullable=True) # Predicted performance
|
||||
optimization_score = Column(Float, nullable=True) # Optimization effectiveness score
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
event = relationship("CalendarEvent")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ContentOptimization(id={self.id}, score={self.optimization_score})>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'event_id': self.event_id,
|
||||
'user_id': self.user_id,
|
||||
'original_content': self.original_content,
|
||||
'optimized_content': self.optimized_content,
|
||||
'platform_adaptations': self.platform_adaptations,
|
||||
'visual_recommendations': self.visual_recommendations,
|
||||
'hashtag_suggestions': self.hashtag_suggestions,
|
||||
'keyword_optimization': self.keyword_optimization,
|
||||
'tone_adjustments': self.tone_adjustments,
|
||||
'length_optimization': self.length_optimization,
|
||||
'performance_prediction': self.performance_prediction,
|
||||
'optimization_score': self.optimization_score,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
|
||||
class CalendarGenerationSession(Base):
|
||||
"""AI calendar generation sessions and results."""
|
||||
|
||||
__tablename__ = "calendar_generation_sessions"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, nullable=False)
|
||||
strategy_id = Column(Integer, ForeignKey("content_strategies.id"), nullable=True)
|
||||
session_type = Column(String(50), nullable=False) # monthly, weekly, custom
|
||||
generation_params = Column(JSON, nullable=True) # Parameters used for generation
|
||||
generated_calendar = Column(JSON, nullable=True) # Generated calendar data
|
||||
ai_insights = Column(JSON, nullable=True) # AI insights and recommendations
|
||||
performance_predictions = Column(JSON, nullable=True) # Performance predictions
|
||||
content_themes = Column(JSON, nullable=True) # Content themes and pillars
|
||||
platform_distribution = Column(JSON, nullable=True) # Platform content distribution
|
||||
optimal_schedule = Column(JSON, nullable=True) # Optimal posting schedule
|
||||
repurposing_opportunities = Column(JSON, nullable=True) # Content repurposing
|
||||
generation_status = Column(String(20), default="processing") # processing, completed, failed
|
||||
ai_confidence = Column(Float, nullable=True) # Overall AI confidence
|
||||
processing_time = Column(Float, nullable=True) # Processing time in seconds
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
strategy = relationship("ContentStrategy")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<CalendarGenerationSession(id={self.id}, type='{self.session_type}', status='{self.generation_status}')>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'user_id': self.user_id,
|
||||
'strategy_id': self.strategy_id,
|
||||
'session_type': self.session_type,
|
||||
'generation_params': self.generation_params,
|
||||
'generated_calendar': self.generated_calendar,
|
||||
'ai_insights': self.ai_insights,
|
||||
'performance_predictions': self.performance_predictions,
|
||||
'content_themes': self.content_themes,
|
||||
'platform_distribution': self.platform_distribution,
|
||||
'optimal_schedule': self.optimal_schedule,
|
||||
'repurposing_opportunities': self.repurposing_opportunities,
|
||||
'generation_status': self.generation_status,
|
||||
'ai_confidence': self.ai_confidence,
|
||||
'processing_time': self.processing_time,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
280
backend/models/enhanced_strategy_models.py
Normal file
280
backend/models/enhanced_strategy_models.py
Normal file
@@ -0,0 +1,280 @@
|
||||
"""
|
||||
Enhanced Strategy Database Models
|
||||
Defines the enhanced database schema for content strategy with 30+ strategic inputs.
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, Float, JSON, ForeignKey, Boolean
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class EnhancedContentStrategy(Base):
|
||||
"""Enhanced Content Strategy model with 30+ strategic inputs."""
|
||||
|
||||
__tablename__ = "enhanced_content_strategies"
|
||||
|
||||
# Primary fields
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, nullable=False)
|
||||
name = Column(String(255), nullable=False)
|
||||
industry = Column(String(100), nullable=True)
|
||||
|
||||
# Business Context (8 inputs)
|
||||
business_objectives = Column(JSON, nullable=True) # Primary and secondary business goals
|
||||
target_metrics = Column(JSON, nullable=True) # KPIs and success metrics
|
||||
content_budget = Column(Float, nullable=True) # Monthly/annual content budget
|
||||
team_size = Column(Integer, nullable=True) # Content team size
|
||||
implementation_timeline = Column(String(100), nullable=True) # 3 months, 6 months, 1 year, etc.
|
||||
market_share = Column(String(50), nullable=True) # Current market share percentage
|
||||
competitive_position = Column(String(50), nullable=True) # Leader, challenger, niche, emerging
|
||||
performance_metrics = Column(JSON, nullable=True) # Current performance data
|
||||
|
||||
# Audience Intelligence (6 inputs)
|
||||
content_preferences = Column(JSON, nullable=True) # Preferred content formats and topics
|
||||
consumption_patterns = Column(JSON, nullable=True) # When and how audience consumes content
|
||||
audience_pain_points = Column(JSON, nullable=True) # Key challenges and pain points
|
||||
buying_journey = Column(JSON, nullable=True) # Customer journey stages and touchpoints
|
||||
seasonal_trends = Column(JSON, nullable=True) # Seasonal content opportunities
|
||||
engagement_metrics = Column(JSON, nullable=True) # Current engagement data
|
||||
|
||||
# Competitive Intelligence (5 inputs)
|
||||
top_competitors = Column(JSON, nullable=True) # List of main competitors
|
||||
competitor_content_strategies = Column(JSON, nullable=True) # Analysis of competitor approaches
|
||||
market_gaps = Column(JSON, nullable=True) # Identified market opportunities
|
||||
industry_trends = Column(JSON, nullable=True) # Current industry trends
|
||||
emerging_trends = Column(JSON, nullable=True) # Upcoming trends and opportunities
|
||||
|
||||
# Content Strategy (7 inputs)
|
||||
preferred_formats = Column(JSON, nullable=True) # Blog posts, videos, infographics, etc.
|
||||
content_mix = Column(JSON, nullable=True) # Distribution of content types
|
||||
content_frequency = Column(String(50), nullable=True) # Daily, weekly, monthly, etc.
|
||||
optimal_timing = Column(JSON, nullable=True) # Best times for publishing
|
||||
quality_metrics = Column(JSON, nullable=True) # Content quality standards
|
||||
editorial_guidelines = Column(JSON, nullable=True) # Style and tone guidelines
|
||||
brand_voice = Column(JSON, nullable=True) # Brand personality and voice
|
||||
|
||||
# Performance & Analytics (4 inputs)
|
||||
traffic_sources = Column(JSON, nullable=True) # Primary traffic sources
|
||||
conversion_rates = Column(JSON, nullable=True) # Current conversion data
|
||||
content_roi_targets = Column(JSON, nullable=True) # ROI goals and targets
|
||||
ab_testing_capabilities = Column(Boolean, default=False) # A/B testing availability
|
||||
|
||||
# Legacy fields for backward compatibility
|
||||
target_audience = Column(JSON, nullable=True) # Store audience demographics and preferences
|
||||
content_pillars = Column(JSON, nullable=True) # Store content pillar definitions
|
||||
ai_recommendations = Column(JSON, nullable=True) # Store AI-generated recommendations
|
||||
|
||||
# Enhanced AI Analysis fields
|
||||
comprehensive_ai_analysis = Column(JSON, nullable=True) # Enhanced AI analysis results
|
||||
onboarding_data_used = Column(JSON, nullable=True) # Track onboarding data integration
|
||||
strategic_scores = Column(JSON, nullable=True) # Strategic performance scores
|
||||
market_positioning = Column(JSON, nullable=True) # Market positioning analysis
|
||||
competitive_advantages = Column(JSON, nullable=True) # Identified competitive advantages
|
||||
strategic_risks = Column(JSON, nullable=True) # Risk assessment
|
||||
opportunity_analysis = Column(JSON, nullable=True) # Opportunity identification
|
||||
|
||||
# Metadata
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
completion_percentage = Column(Float, default=0.0) # Track input completion
|
||||
data_source_transparency = Column(JSON, nullable=True) # Track data sources for auto-population
|
||||
|
||||
def __repr__(self):
|
||||
return f"<EnhancedContentStrategy(id={self.id}, name='{self.name}', industry='{self.industry}')>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert model to dictionary with enhanced structure."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'user_id': self.user_id,
|
||||
'name': self.name,
|
||||
'industry': self.industry,
|
||||
|
||||
# Business Context
|
||||
'business_objectives': self.business_objectives,
|
||||
'target_metrics': self.target_metrics,
|
||||
'content_budget': self.content_budget,
|
||||
'team_size': self.team_size,
|
||||
'implementation_timeline': self.implementation_timeline,
|
||||
'market_share': self.market_share,
|
||||
'competitive_position': self.competitive_position,
|
||||
'performance_metrics': self.performance_metrics,
|
||||
|
||||
# Audience Intelligence
|
||||
'content_preferences': self.content_preferences,
|
||||
'consumption_patterns': self.consumption_patterns,
|
||||
'audience_pain_points': self.audience_pain_points,
|
||||
'buying_journey': self.buying_journey,
|
||||
'seasonal_trends': self.seasonal_trends,
|
||||
'engagement_metrics': self.engagement_metrics,
|
||||
|
||||
# Competitive Intelligence
|
||||
'top_competitors': self.top_competitors,
|
||||
'competitor_content_strategies': self.competitor_content_strategies,
|
||||
'market_gaps': self.market_gaps,
|
||||
'industry_trends': self.industry_trends,
|
||||
'emerging_trends': self.emerging_trends,
|
||||
|
||||
# Content Strategy
|
||||
'preferred_formats': self.preferred_formats,
|
||||
'content_mix': self.content_mix,
|
||||
'content_frequency': self.content_frequency,
|
||||
'optimal_timing': self.optimal_timing,
|
||||
'quality_metrics': self.quality_metrics,
|
||||
'editorial_guidelines': self.editorial_guidelines,
|
||||
'brand_voice': self.brand_voice,
|
||||
|
||||
# Performance & Analytics
|
||||
'traffic_sources': self.traffic_sources,
|
||||
'conversion_rates': self.conversion_rates,
|
||||
'content_roi_targets': self.content_roi_targets,
|
||||
'ab_testing_capabilities': self.ab_testing_capabilities,
|
||||
|
||||
# Legacy fields
|
||||
'target_audience': self.target_audience,
|
||||
'content_pillars': self.content_pillars,
|
||||
'ai_recommendations': self.ai_recommendations,
|
||||
|
||||
# Enhanced AI Analysis
|
||||
'comprehensive_ai_analysis': self.comprehensive_ai_analysis,
|
||||
'onboarding_data_used': self.onboarding_data_used,
|
||||
'strategic_scores': self.strategic_scores,
|
||||
'market_positioning': self.market_positioning,
|
||||
'competitive_advantages': self.competitive_advantages,
|
||||
'strategic_risks': self.strategic_risks,
|
||||
'opportunity_analysis': self.opportunity_analysis,
|
||||
|
||||
# Metadata
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
||||
'completion_percentage': self.completion_percentage,
|
||||
'data_source_transparency': self.data_source_transparency
|
||||
}
|
||||
|
||||
def calculate_completion_percentage(self):
|
||||
"""Calculate the percentage of required fields that have been filled."""
|
||||
required_fields = [
|
||||
'business_objectives', 'target_metrics', 'content_budget', 'team_size',
|
||||
'implementation_timeline', 'market_share', 'competitive_position',
|
||||
'content_preferences', 'consumption_patterns', 'audience_pain_points',
|
||||
'buying_journey', 'seasonal_trends', 'engagement_metrics',
|
||||
'top_competitors', 'competitor_content_strategies', 'market_gaps',
|
||||
'industry_trends', 'emerging_trends', 'preferred_formats',
|
||||
'content_mix', 'content_frequency', 'optimal_timing',
|
||||
'quality_metrics', 'editorial_guidelines', 'brand_voice',
|
||||
'traffic_sources', 'conversion_rates', 'content_roi_targets'
|
||||
]
|
||||
|
||||
filled_fields = sum(1 for field in required_fields if getattr(self, field) is not None)
|
||||
self.completion_percentage = (filled_fields / len(required_fields)) * 100
|
||||
return self.completion_percentage
|
||||
|
||||
class EnhancedAIAnalysisResult(Base):
|
||||
"""Enhanced AI Analysis Result model for storing comprehensive AI-generated insights."""
|
||||
|
||||
__tablename__ = "enhanced_ai_analysis_results"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, nullable=False)
|
||||
strategy_id = Column(Integer, ForeignKey("enhanced_content_strategies.id"), nullable=True)
|
||||
|
||||
# Analysis type for the 5 specialized prompts
|
||||
analysis_type = Column(String(50), nullable=False) # comprehensive_strategy, audience_intelligence, competitive_intelligence, performance_optimization, content_calendar_optimization
|
||||
|
||||
# Comprehensive analysis results
|
||||
comprehensive_insights = Column(JSON, nullable=True) # Holistic strategy insights
|
||||
audience_intelligence = Column(JSON, nullable=True) # Detailed audience analysis
|
||||
competitive_intelligence = Column(JSON, nullable=True) # Competitive landscape analysis
|
||||
performance_optimization = Column(JSON, nullable=True) # Performance improvement recommendations
|
||||
content_calendar_optimization = Column(JSON, nullable=True) # Calendar optimization insights
|
||||
|
||||
# Enhanced data tracking
|
||||
onboarding_data_used = Column(JSON, nullable=True) # Track onboarding data integration
|
||||
data_confidence_scores = Column(JSON, nullable=True) # Confidence scores for data sources
|
||||
recommendation_quality_scores = Column(JSON, nullable=True) # Quality scores for recommendations
|
||||
|
||||
# Performance metrics
|
||||
processing_time = Column(Float, nullable=True) # Processing time in seconds
|
||||
ai_service_status = Column(String(20), default="operational") # operational, fallback, error
|
||||
prompt_version = Column(String(50), nullable=True) # Version of AI prompt used
|
||||
|
||||
# Metadata
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<EnhancedAIAnalysisResult(id={self.id}, type='{self.analysis_type}', user_id={self.user_id})>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert model to dictionary."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'user_id': self.user_id,
|
||||
'strategy_id': self.strategy_id,
|
||||
'analysis_type': self.analysis_type,
|
||||
'comprehensive_insights': self.comprehensive_insights,
|
||||
'audience_intelligence': self.audience_intelligence,
|
||||
'competitive_intelligence': self.competitive_intelligence,
|
||||
'performance_optimization': self.performance_optimization,
|
||||
'content_calendar_optimization': self.content_calendar_optimization,
|
||||
'onboarding_data_used': self.onboarding_data_used,
|
||||
'data_confidence_scores': self.data_confidence_scores,
|
||||
'recommendation_quality_scores': self.recommendation_quality_scores,
|
||||
'processing_time': self.processing_time,
|
||||
'ai_service_status': self.ai_service_status,
|
||||
'prompt_version': self.prompt_version,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
|
||||
class OnboardingDataIntegration(Base):
|
||||
"""Model for tracking onboarding data integration with enhanced strategy."""
|
||||
|
||||
__tablename__ = "onboarding_data_integrations"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, nullable=False)
|
||||
strategy_id = Column(Integer, ForeignKey("enhanced_content_strategies.id"), nullable=True)
|
||||
|
||||
# Onboarding data sources
|
||||
website_analysis_data = Column(JSON, nullable=True) # Data from website analysis
|
||||
research_preferences_data = Column(JSON, nullable=True) # Data from research preferences
|
||||
api_keys_data = Column(JSON, nullable=True) # API configuration data
|
||||
|
||||
# Integration mapping
|
||||
field_mappings = Column(JSON, nullable=True) # Mapping of onboarding fields to strategy fields
|
||||
auto_populated_fields = Column(JSON, nullable=True) # Fields auto-populated from onboarding
|
||||
user_overrides = Column(JSON, nullable=True) # Fields manually overridden by user
|
||||
|
||||
# Data quality and confidence
|
||||
data_quality_scores = Column(JSON, nullable=True) # Quality scores for each data source
|
||||
confidence_levels = Column(JSON, nullable=True) # Confidence levels for auto-populated data
|
||||
data_freshness = Column(JSON, nullable=True) # How recent the onboarding data is
|
||||
|
||||
# Metadata
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<OnboardingDataIntegration(id={self.id}, user_id={self.user_id}, strategy_id={self.strategy_id})>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert model to dictionary."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'user_id': self.user_id,
|
||||
'strategy_id': self.strategy_id,
|
||||
'website_analysis_data': self.website_analysis_data,
|
||||
'research_preferences_data': self.research_preferences_data,
|
||||
'api_keys_data': self.api_keys_data,
|
||||
'field_mappings': self.field_mappings,
|
||||
'auto_populated_fields': self.auto_populated_fields,
|
||||
'user_overrides': self.user_overrides,
|
||||
'data_quality_scores': self.data_quality_scores,
|
||||
'confidence_levels': self.confidence_levels,
|
||||
'data_freshness': self.data_freshness,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
146
backend/models/onboarding.py
Normal file
146
backend/models/onboarding.py
Normal file
@@ -0,0 +1,146 @@
|
||||
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, func, JSON, Text, Boolean
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship
|
||||
import datetime
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class OnboardingSession(Base):
|
||||
__tablename__ = 'onboarding_sessions'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, nullable=False) # Replace with ForeignKey if you have a user table
|
||||
current_step = Column(Integer, default=1)
|
||||
progress = Column(Float, default=0.0)
|
||||
started_at = Column(DateTime, default=func.now())
|
||||
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
|
||||
api_keys = relationship('APIKey', back_populates='session', cascade="all, delete-orphan")
|
||||
website_analyses = relationship('WebsiteAnalysis', back_populates='session', cascade="all, delete-orphan")
|
||||
research_preferences = relationship('ResearchPreferences', back_populates='session', cascade="all, delete-orphan", uselist=False)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<OnboardingSession(id={self.id}, user_id={self.user_id}, step={self.current_step}, progress={self.progress})>"
|
||||
|
||||
class APIKey(Base):
|
||||
__tablename__ = 'api_keys'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
session_id = Column(Integer, ForeignKey('onboarding_sessions.id'))
|
||||
provider = Column(String(64), nullable=False)
|
||||
key = Column(String(256), nullable=False)
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
|
||||
session = relationship('OnboardingSession', back_populates='api_keys')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<APIKey(id={self.id}, provider={self.provider}, session_id={self.session_id})>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert to dictionary for API responses."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'session_id': self.session_id,
|
||||
'provider': self.provider,
|
||||
'key': self.key, # Note: In production, you might want to mask this
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
|
||||
class WebsiteAnalysis(Base):
|
||||
"""Stores website analysis results from onboarding step 2."""
|
||||
__tablename__ = 'website_analyses'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
session_id = Column(Integer, ForeignKey('onboarding_sessions.id', ondelete='CASCADE'), nullable=False)
|
||||
website_url = Column(String(500), nullable=False)
|
||||
analysis_date = Column(DateTime, default=func.now())
|
||||
|
||||
# Style analysis results
|
||||
writing_style = Column(JSON) # Tone, voice, complexity, engagement_level
|
||||
content_characteristics = Column(JSON) # Sentence structure, vocabulary, paragraph organization
|
||||
target_audience = Column(JSON) # Demographics, expertise level, industry focus
|
||||
content_type = Column(JSON) # Primary type, secondary types, purpose
|
||||
recommended_settings = Column(JSON) # Writing tone, target audience, content type
|
||||
|
||||
# Crawl results
|
||||
crawl_result = Column(JSON) # Raw crawl data
|
||||
style_patterns = Column(JSON) # Writing patterns analysis
|
||||
style_guidelines = Column(JSON) # Generated guidelines
|
||||
|
||||
# Metadata
|
||||
status = Column(String(50), default='completed') # completed, failed, in_progress
|
||||
error_message = Column(Text)
|
||||
warning_message = Column(Text)
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
|
||||
|
||||
# Relationships
|
||||
session = relationship('OnboardingSession', back_populates='website_analyses')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<WebsiteAnalysis(id={self.id}, url={self.website_url}, status={self.status})>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert to dictionary for API responses."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'website_url': self.website_url,
|
||||
'analysis_date': self.analysis_date.isoformat() if self.analysis_date else None,
|
||||
'writing_style': self.writing_style,
|
||||
'content_characteristics': self.content_characteristics,
|
||||
'target_audience': self.target_audience,
|
||||
'content_type': self.content_type,
|
||||
'recommended_settings': self.recommended_settings,
|
||||
'crawl_result': self.crawl_result,
|
||||
'style_patterns': self.style_patterns,
|
||||
'style_guidelines': self.style_guidelines,
|
||||
'status': self.status,
|
||||
'error_message': self.error_message,
|
||||
'warning_message': self.warning_message,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
|
||||
class ResearchPreferences(Base):
|
||||
"""Stores research preferences from onboarding step 3."""
|
||||
__tablename__ = 'research_preferences'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
session_id = Column(Integer, ForeignKey('onboarding_sessions.id', ondelete='CASCADE'), nullable=False)
|
||||
|
||||
# Research configuration
|
||||
research_depth = Column(String(50), nullable=False) # Basic, Standard, Comprehensive, Expert
|
||||
content_types = Column(JSON, nullable=False) # Array of content types
|
||||
auto_research = Column(Boolean, default=True)
|
||||
factual_content = Column(Boolean, default=True)
|
||||
|
||||
# Style detection data (from step 2)
|
||||
writing_style = Column(JSON) # Tone, voice, complexity from website analysis
|
||||
content_characteristics = Column(JSON) # Sentence structure, vocabulary from analysis
|
||||
target_audience = Column(JSON) # Demographics, expertise level from analysis
|
||||
recommended_settings = Column(JSON) # AI-generated recommendations from analysis
|
||||
|
||||
# Metadata
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
|
||||
|
||||
# Relationships
|
||||
session = relationship('OnboardingSession', back_populates='research_preferences')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ResearchPreferences(id={self.id}, session_id={self.session_id}, depth={self.research_depth})>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert to dictionary for API responses."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'session_id': self.session_id,
|
||||
'research_depth': self.research_depth,
|
||||
'content_types': self.content_types,
|
||||
'auto_research': self.auto_research,
|
||||
'factual_content': self.factual_content,
|
||||
'writing_style': self.writing_style,
|
||||
'content_characteristics': self.content_characteristics,
|
||||
'target_audience': self.target_audience,
|
||||
'recommended_settings': self.recommended_settings,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
418
backend/models/seo_analysis.py
Normal file
418
backend/models/seo_analysis.py
Normal file
@@ -0,0 +1,418 @@
|
||||
"""
|
||||
Database models for SEO analysis data storage
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Text, JSON, Float, Boolean, ForeignKey
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, List
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class SEOAnalysis(Base):
|
||||
"""Main SEO analysis record"""
|
||||
__tablename__ = 'seo_analyses'
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
url = Column(String(500), nullable=False, index=True)
|
||||
overall_score = Column(Integer, nullable=False)
|
||||
health_status = Column(String(50), nullable=False) # excellent, good, needs_improvement, poor, error
|
||||
timestamp = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
analysis_data = Column(JSON, nullable=True) # Store complete analysis data
|
||||
|
||||
# Relationships
|
||||
critical_issues = relationship("SEOIssue", back_populates="analysis", cascade="all, delete-orphan")
|
||||
warnings = relationship("SEOWarning", back_populates="analysis", cascade="all, delete-orphan")
|
||||
recommendations = relationship("SEORecommendation", back_populates="analysis", cascade="all, delete-orphan")
|
||||
category_scores = relationship("SEOCategoryScore", back_populates="analysis", cascade="all, delete-orphan")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SEOAnalysis(url='{self.url}', score={self.overall_score}, status='{self.health_status}')>"
|
||||
|
||||
class SEOIssue(Base):
|
||||
"""Critical SEO issues"""
|
||||
__tablename__ = 'seo_issues'
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
|
||||
issue_text = Column(Text, nullable=False)
|
||||
category = Column(String(100), nullable=True) # url_structure, meta_data, content, etc.
|
||||
priority = Column(String(20), default='critical') # critical, high, medium, low
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# Relationship
|
||||
analysis = relationship("SEOAnalysis", back_populates="critical_issues")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SEOIssue(category='{self.category}', priority='{self.priority}')>"
|
||||
|
||||
class SEOWarning(Base):
|
||||
"""SEO warnings"""
|
||||
__tablename__ = 'seo_warnings'
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
|
||||
warning_text = Column(Text, nullable=False)
|
||||
category = Column(String(100), nullable=True)
|
||||
priority = Column(String(20), default='medium')
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# Relationship
|
||||
analysis = relationship("SEOAnalysis", back_populates="warnings")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SEOWarning(category='{self.category}', priority='{self.priority}')>"
|
||||
|
||||
class SEORecommendation(Base):
|
||||
"""SEO recommendations"""
|
||||
__tablename__ = 'seo_recommendations'
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
|
||||
recommendation_text = Column(Text, nullable=False)
|
||||
category = Column(String(100), nullable=True)
|
||||
difficulty = Column(String(20), default='medium') # easy, medium, hard
|
||||
estimated_impact = Column(String(20), default='medium') # high, medium, low
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# Relationship
|
||||
analysis = relationship("SEOAnalysis", back_populates="recommendations")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SEORecommendation(category='{self.category}', difficulty='{self.difficulty}')>"
|
||||
|
||||
class SEOCategoryScore(Base):
|
||||
"""Individual category scores"""
|
||||
__tablename__ = 'seo_category_scores'
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
|
||||
category = Column(String(100), nullable=False) # url_structure, meta_data, content, etc.
|
||||
score = Column(Integer, nullable=False)
|
||||
max_score = Column(Integer, default=100)
|
||||
details = Column(JSON, nullable=True) # Store category-specific details
|
||||
|
||||
# Relationship
|
||||
analysis = relationship("SEOAnalysis", back_populates="category_scores")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SEOCategoryScore(category='{self.category}', score={self.score})>"
|
||||
|
||||
class SEOAnalysisHistory(Base):
|
||||
"""Historical SEO analysis data for tracking improvements"""
|
||||
__tablename__ = 'seo_analysis_history'
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
url = Column(String(500), nullable=False, index=True)
|
||||
analysis_date = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
overall_score = Column(Integer, nullable=False)
|
||||
health_status = Column(String(50), nullable=False)
|
||||
score_change = Column(Integer, default=0) # Change from previous analysis
|
||||
|
||||
# Category scores for tracking
|
||||
url_structure_score = Column(Integer, nullable=True)
|
||||
meta_data_score = Column(Integer, nullable=True)
|
||||
content_score = Column(Integer, nullable=True)
|
||||
technical_score = Column(Integer, nullable=True)
|
||||
performance_score = Column(Integer, nullable=True)
|
||||
accessibility_score = Column(Integer, nullable=True)
|
||||
user_experience_score = Column(Integer, nullable=True)
|
||||
security_score = Column(Integer, nullable=True)
|
||||
|
||||
# Issue counts
|
||||
critical_issues_count = Column(Integer, default=0)
|
||||
warnings_count = Column(Integer, default=0)
|
||||
recommendations_count = Column(Integer, default=0)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SEOAnalysisHistory(url='{self.url}', score={self.overall_score}, date='{self.analysis_date}')>"
|
||||
|
||||
class SEOKeywordAnalysis(Base):
|
||||
"""Keyword analysis data"""
|
||||
__tablename__ = 'seo_keyword_analyses'
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
|
||||
keyword = Column(String(200), nullable=False)
|
||||
density = Column(Float, nullable=True)
|
||||
count = Column(Integer, default=0)
|
||||
in_title = Column(Boolean, default=False)
|
||||
in_headings = Column(Boolean, default=False)
|
||||
in_alt_text = Column(Boolean, default=False)
|
||||
in_meta_description = Column(Boolean, default=False)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SEOKeywordAnalysis(keyword='{self.keyword}', density={self.density})>"
|
||||
|
||||
class SEOTechnicalData(Base):
|
||||
"""Technical SEO data"""
|
||||
__tablename__ = 'seo_technical_data'
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
|
||||
|
||||
# Meta data
|
||||
title = Column(Text, nullable=True)
|
||||
title_length = Column(Integer, nullable=True)
|
||||
meta_description = Column(Text, nullable=True)
|
||||
meta_description_length = Column(Integer, nullable=True)
|
||||
|
||||
# Technical elements
|
||||
has_canonical = Column(Boolean, default=False)
|
||||
canonical_url = Column(String(500), nullable=True)
|
||||
has_schema_markup = Column(Boolean, default=False)
|
||||
schema_types = Column(JSON, nullable=True)
|
||||
has_hreflang = Column(Boolean, default=False)
|
||||
hreflang_data = Column(JSON, nullable=True)
|
||||
|
||||
# Social media
|
||||
og_tags_count = Column(Integer, default=0)
|
||||
twitter_tags_count = Column(Integer, default=0)
|
||||
|
||||
# Technical files
|
||||
robots_txt_exists = Column(Boolean, default=False)
|
||||
sitemap_exists = Column(Boolean, default=False)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SEOTechnicalData(title_length={self.title_length}, has_schema={self.has_schema_markup})>"
|
||||
|
||||
class SEOContentData(Base):
|
||||
"""Content analysis data"""
|
||||
__tablename__ = 'seo_content_data'
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
|
||||
|
||||
# Content metrics
|
||||
word_count = Column(Integer, default=0)
|
||||
char_count = Column(Integer, default=0)
|
||||
headings_count = Column(Integer, default=0)
|
||||
h1_count = Column(Integer, default=0)
|
||||
h2_count = Column(Integer, default=0)
|
||||
|
||||
# Media
|
||||
images_count = Column(Integer, default=0)
|
||||
images_with_alt = Column(Integer, default=0)
|
||||
images_without_alt = Column(Integer, default=0)
|
||||
|
||||
# Links
|
||||
internal_links_count = Column(Integer, default=0)
|
||||
external_links_count = Column(Integer, default=0)
|
||||
|
||||
# Quality metrics
|
||||
readability_score = Column(Float, nullable=True)
|
||||
spelling_errors = Column(Integer, default=0)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SEOContentData(word_count={self.word_count}, readability={self.readability_score})>"
|
||||
|
||||
class SEOPerformanceData(Base):
|
||||
"""Performance analysis data"""
|
||||
__tablename__ = 'seo_performance_data'
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
|
||||
|
||||
# Load time
|
||||
load_time = Column(Float, nullable=True)
|
||||
|
||||
# Compression
|
||||
is_compressed = Column(Boolean, default=False)
|
||||
compression_type = Column(String(50), nullable=True) # gzip, br, etc.
|
||||
|
||||
# Caching
|
||||
has_cache_headers = Column(Boolean, default=False)
|
||||
cache_control = Column(String(200), nullable=True)
|
||||
|
||||
# HTTP headers
|
||||
content_encoding = Column(String(100), nullable=True)
|
||||
server_info = Column(String(200), nullable=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SEOPerformanceData(load_time={self.load_time}, compressed={self.is_compressed})>"
|
||||
|
||||
class SEOAccessibilityData(Base):
|
||||
"""Accessibility analysis data"""
|
||||
__tablename__ = 'seo_accessibility_data'
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
|
||||
|
||||
# Alt text
|
||||
images_with_alt = Column(Integer, default=0)
|
||||
images_without_alt = Column(Integer, default=0)
|
||||
alt_text_ratio = Column(Float, nullable=True)
|
||||
|
||||
# Forms
|
||||
form_fields_count = Column(Integer, default=0)
|
||||
labeled_fields_count = Column(Integer, default=0)
|
||||
label_ratio = Column(Float, nullable=True)
|
||||
|
||||
# ARIA
|
||||
aria_elements_count = Column(Integer, default=0)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SEOAccessibilityData(alt_ratio={self.alt_text_ratio}, aria_count={self.aria_elements_count})>"
|
||||
|
||||
class SEOUserExperienceData(Base):
|
||||
"""User experience analysis data"""
|
||||
__tablename__ = 'seo_user_experience_data'
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
|
||||
|
||||
# Mobile
|
||||
is_mobile_friendly = Column(Boolean, default=False)
|
||||
has_viewport = Column(Boolean, default=False)
|
||||
|
||||
# CTAs
|
||||
ctas_found = Column(JSON, nullable=True) # List of found CTAs
|
||||
cta_count = Column(Integer, default=0)
|
||||
|
||||
# Navigation
|
||||
has_navigation = Column(Boolean, default=False)
|
||||
nav_elements_count = Column(Integer, default=0)
|
||||
|
||||
# Contact info
|
||||
has_contact_info = Column(Boolean, default=False)
|
||||
|
||||
# Social media
|
||||
social_links_count = Column(Integer, default=0)
|
||||
social_links = Column(JSON, nullable=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SEOUserExperienceData(mobile_friendly={self.is_mobile_friendly}, cta_count={self.cta_count})>"
|
||||
|
||||
class SEOSecurityData(Base):
|
||||
"""Security headers analysis data"""
|
||||
__tablename__ = 'seo_security_data'
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
|
||||
|
||||
# Security headers
|
||||
has_x_frame_options = Column(Boolean, default=False)
|
||||
has_x_content_type_options = Column(Boolean, default=False)
|
||||
has_x_xss_protection = Column(Boolean, default=False)
|
||||
has_strict_transport_security = Column(Boolean, default=False)
|
||||
has_content_security_policy = Column(Boolean, default=False)
|
||||
has_referrer_policy = Column(Boolean, default=False)
|
||||
|
||||
# HTTPS
|
||||
is_https = Column(Boolean, default=False)
|
||||
|
||||
# Total security score
|
||||
security_score = Column(Integer, default=0)
|
||||
present_headers = Column(JSON, nullable=True)
|
||||
missing_headers = Column(JSON, nullable=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SEOSecurityData(score={self.security_score}, https={self.is_https})>"
|
||||
|
||||
# Helper functions for data conversion
|
||||
def create_analysis_from_result(result: 'SEOAnalysisResult') -> SEOAnalysis:
|
||||
"""Create SEOAnalysis record from analysis result"""
|
||||
return SEOAnalysis(
|
||||
url=result.url,
|
||||
overall_score=result.overall_score,
|
||||
health_status=result.health_status,
|
||||
timestamp=result.timestamp,
|
||||
analysis_data=result.data
|
||||
)
|
||||
|
||||
def create_issues_from_result(analysis_id: int, result: 'SEOAnalysisResult') -> List[SEOIssue]:
|
||||
"""Create SEOIssue records from analysis result"""
|
||||
issues = []
|
||||
for issue_data in result.critical_issues:
|
||||
# Handle both string and dictionary formats
|
||||
if isinstance(issue_data, dict):
|
||||
issue_text = issue_data.get('message', str(issue_data))
|
||||
category = issue_data.get('category', extract_category_from_text(issue_text))
|
||||
else:
|
||||
issue_text = str(issue_data)
|
||||
category = extract_category_from_text(issue_text)
|
||||
|
||||
issues.append(SEOIssue(
|
||||
analysis_id=analysis_id,
|
||||
issue_text=issue_text,
|
||||
category=category,
|
||||
priority='critical'
|
||||
))
|
||||
return issues
|
||||
|
||||
def create_warnings_from_result(analysis_id: int, result: 'SEOAnalysisResult') -> List[SEOWarning]:
|
||||
"""Create SEOWarning records from analysis result"""
|
||||
warnings = []
|
||||
for warning_data in result.warnings:
|
||||
# Handle both string and dictionary formats
|
||||
if isinstance(warning_data, dict):
|
||||
warning_text = warning_data.get('message', str(warning_data))
|
||||
category = warning_data.get('category', extract_category_from_text(warning_text))
|
||||
else:
|
||||
warning_text = str(warning_data)
|
||||
category = extract_category_from_text(warning_text)
|
||||
|
||||
warnings.append(SEOWarning(
|
||||
analysis_id=analysis_id,
|
||||
warning_text=warning_text,
|
||||
category=category,
|
||||
priority='medium'
|
||||
))
|
||||
return warnings
|
||||
|
||||
def create_recommendations_from_result(analysis_id: int, result: 'SEOAnalysisResult') -> List[SEORecommendation]:
|
||||
"""Create SEORecommendation records from analysis result"""
|
||||
recommendations = []
|
||||
for rec_data in result.recommendations:
|
||||
# Handle both string and dictionary formats
|
||||
if isinstance(rec_data, dict):
|
||||
rec_text = rec_data.get('message', str(rec_data))
|
||||
category = rec_data.get('category', extract_category_from_text(rec_text))
|
||||
else:
|
||||
rec_text = str(rec_data)
|
||||
category = extract_category_from_text(rec_text)
|
||||
|
||||
recommendations.append(SEORecommendation(
|
||||
analysis_id=analysis_id,
|
||||
recommendation_text=rec_text,
|
||||
category=category,
|
||||
difficulty='medium',
|
||||
estimated_impact='medium'
|
||||
))
|
||||
return recommendations
|
||||
|
||||
def create_category_scores_from_result(analysis_id: int, result: 'SEOAnalysisResult') -> List[SEOCategoryScore]:
|
||||
"""Create SEOCategoryScore records from analysis result"""
|
||||
scores = []
|
||||
for category, data in result.data.items():
|
||||
if isinstance(data, dict) and 'score' in data:
|
||||
scores.append(SEOCategoryScore(
|
||||
analysis_id=analysis_id,
|
||||
category=category,
|
||||
score=data['score'],
|
||||
max_score=100,
|
||||
details=data
|
||||
))
|
||||
return scores
|
||||
|
||||
def extract_category_from_text(text: str) -> str:
|
||||
"""Extract category from issue/warning/recommendation text"""
|
||||
text_lower = text.lower()
|
||||
|
||||
if any(word in text_lower for word in ['title', 'meta', 'description']):
|
||||
return 'meta_data'
|
||||
elif any(word in text_lower for word in ['https', 'url', 'security']):
|
||||
return 'url_structure'
|
||||
elif any(word in text_lower for word in ['content', 'word', 'heading', 'image']):
|
||||
return 'content_analysis'
|
||||
elif any(word in text_lower for word in ['schema', 'canonical', 'technical']):
|
||||
return 'technical_seo'
|
||||
elif any(word in text_lower for word in ['speed', 'load', 'performance']):
|
||||
return 'performance'
|
||||
elif any(word in text_lower for word in ['alt', 'accessibility', 'aria']):
|
||||
return 'accessibility'
|
||||
elif any(word in text_lower for word in ['mobile', 'cta', 'navigation']):
|
||||
return 'user_experience'
|
||||
else:
|
||||
return 'general'
|
||||
Reference in New Issue
Block a user