""" A/B Test Models - Test different content variants to optimize engagement. """ from datetime import datetime from sqlalchemy import Column, Integer, String, Text, Float, Boolean, DateTime, ForeignKey, JSON, Enum from sqlalchemy.orm import relationship import enum from app.core.database import Base class ABTestStatus(enum.Enum): """Status options for A/B tests.""" DRAFT = "draft" RUNNING = "running" COMPLETED = "completed" CANCELLED = "cancelled" class ABTestType(enum.Enum): """Types of A/B tests.""" CONTENT = "content" # Test different content/copy TIMING = "timing" # Test different posting times HASHTAGS = "hashtags" # Test different hashtag sets IMAGE = "image" # Test different images class ABTest(Base): """ A/B Test model for testing content variations. """ __tablename__ = "ab_tests" id = Column(Integer, primary_key=True, index=True) # Test info name = Column(String(255), nullable=False) description = Column(Text, nullable=True) test_type = Column(String(50), nullable=False, default="content") # Platform targeting platform = Column(String(50), nullable=False, index=True) # x, threads, instagram, facebook # Status status = Column(String(20), default="draft", index=True) # Timing started_at = Column(DateTime, nullable=True) ended_at = Column(DateTime, nullable=True) duration_hours = Column(Integer, default=24) # How long to run the test # Results winning_variant_id = Column(Integer, ForeignKey("ab_test_variants.id"), nullable=True) confidence_level = Column(Float, nullable=True) # Statistical confidence # Configuration min_sample_size = Column(Integer, default=100) # Min impressions per variant success_metric = Column(String(50), default="engagement_rate") # Options: engagement_rate, likes, comments, shares, clicks # Metadata created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) created_by = Column(String(100), nullable=True) # Relationships variants = relationship("ABTestVariant", back_populates="test", foreign_keys="ABTestVariant.test_id") def __repr__(self): return f"" def to_dict(self): """Convert to dictionary.""" return { "id": self.id, "name": self.name, "description": self.description, "test_type": self.test_type, "platform": self.platform, "status": self.status, "started_at": self.started_at.isoformat() if self.started_at else None, "ended_at": self.ended_at.isoformat() if self.ended_at else None, "duration_hours": self.duration_hours, "winning_variant_id": self.winning_variant_id, "confidence_level": self.confidence_level, "min_sample_size": self.min_sample_size, "success_metric": self.success_metric, "variants": [v.to_dict() for v in self.variants] if self.variants else [], "created_at": self.created_at.isoformat() if self.created_at else None } class ABTestVariant(Base): """ Variant within an A/B test. """ __tablename__ = "ab_test_variants" id = Column(Integer, primary_key=True, index=True) # Parent test test_id = Column(Integer, ForeignKey("ab_tests.id", ondelete="CASCADE"), nullable=False) # Variant info name = Column(String(10), nullable=False) # A, B, C, etc. content = Column(Text, nullable=False) hashtags = Column(JSON, nullable=True) image_url = Column(String(500), nullable=True) # Associated post (once published) post_id = Column(Integer, ForeignKey("posts.id"), nullable=True) # Metrics (populated after publishing) impressions = Column(Integer, default=0) reach = Column(Integer, default=0) likes = Column(Integer, default=0) comments = Column(Integer, default=0) shares = Column(Integer, default=0) clicks = Column(Integer, default=0) engagement_rate = Column(Float, default=0.0) # Status is_winner = Column(Boolean, default=False) published_at = Column(DateTime, nullable=True) # Timestamps created_at = Column(DateTime, default=datetime.utcnow) # Relationships test = relationship("ABTest", back_populates="variants", foreign_keys=[test_id]) post = relationship("Post", backref="ab_test_variant") def __repr__(self): return f"" def to_dict(self): """Convert to dictionary.""" return { "id": self.id, "test_id": self.test_id, "name": self.name, "content": self.content[:100] + "..." if len(self.content) > 100 else self.content, "full_content": self.content, "hashtags": self.hashtags, "image_url": self.image_url, "post_id": self.post_id, "impressions": self.impressions, "reach": self.reach, "likes": self.likes, "comments": self.comments, "shares": self.shares, "clicks": self.clicks, "engagement_rate": round(self.engagement_rate, 2), "is_winner": self.is_winner, "published_at": self.published_at.isoformat() if self.published_at else None } def calculate_engagement_rate(self): """Calculate engagement rate for this variant.""" if self.impressions > 0: total_engagements = self.likes + self.comments + self.shares self.engagement_rate = (total_engagements / self.impressions) * 100 return self.engagement_rate