Phase 1 - Analytics y Reportes: - PostMetrics and AnalyticsReport models for tracking engagement - Analytics service with dashboard stats, top posts, optimal times - 8 API endpoints at /api/analytics/* - Interactive dashboard with Chart.js charts - Celery tasks for metrics fetch (15min) and weekly reports Phase 2 - Integración Odoo: - Lead and OdooSyncLog models for CRM integration - Odoo fields added to Product and Service models - XML-RPC service for bidirectional sync - Lead management API at /api/leads/* - Leads dashboard template - Celery tasks for product/service sync and lead export Phase 3 - A/B Testing y Recycling: - ABTest, ABTestVariant, RecycledPost models - Statistical winner analysis using chi-square test - Content recycling with engagement-based scoring - APIs at /api/ab-tests/* and /api/recycling/* - Automated test evaluation and content recycling tasks Phase 4 - Thread Series y Templates: - ThreadSeries and ThreadPost models for multi-post threads - AI-powered thread generation - Enhanced ImageTemplate with HTML template support - APIs at /api/threads/* and /api/templates/* - Thread scheduling with reply chain support Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
75 lines
2.6 KiB
Python
75 lines
2.6 KiB
Python
"""
|
|
Recycled Post Model - Track content recycling for evergreen posts.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, JSON, Boolean
|
|
from sqlalchemy.orm import relationship
|
|
|
|
from app.core.database import Base
|
|
|
|
|
|
class RecycledPost(Base):
|
|
"""
|
|
Tracks when posts are recycled/republished.
|
|
Evergreen content that performed well can be republished with modifications.
|
|
"""
|
|
__tablename__ = "recycled_posts"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
|
|
# Original post reference
|
|
original_post_id = Column(Integer, ForeignKey("posts.id"), nullable=False, index=True)
|
|
|
|
# New post created from recycling
|
|
new_post_id = Column(Integer, ForeignKey("posts.id"), nullable=True, index=True)
|
|
|
|
# Recycle count (how many times this original has been recycled)
|
|
recycle_number = Column(Integer, default=1)
|
|
|
|
# Modifications made
|
|
modifications = Column(JSON, nullable=True)
|
|
# Example: {"content_changed": true, "hashtags_updated": true, "image_changed": false}
|
|
|
|
modification_notes = Column(Text, nullable=True)
|
|
|
|
# Performance comparison
|
|
original_engagement_rate = Column(Integer, nullable=True)
|
|
new_engagement_rate = Column(Integer, nullable=True)
|
|
|
|
# Status
|
|
status = Column(String(20), default="pending")
|
|
# pending, published, cancelled
|
|
|
|
# Reason for recycling
|
|
reason = Column(String(100), nullable=True)
|
|
# high_performer, evergreen, seasonal, manual
|
|
|
|
# Timestamps
|
|
recycled_at = Column(DateTime, default=datetime.utcnow)
|
|
scheduled_for = Column(DateTime, nullable=True)
|
|
|
|
# Relationships
|
|
original_post = relationship("Post", foreign_keys=[original_post_id], backref="recycled_versions")
|
|
new_post = relationship("Post", foreign_keys=[new_post_id])
|
|
|
|
def __repr__(self):
|
|
return f"<RecycledPost {self.id} - Original: {self.original_post_id}>"
|
|
|
|
def to_dict(self):
|
|
"""Convert to dictionary."""
|
|
return {
|
|
"id": self.id,
|
|
"original_post_id": self.original_post_id,
|
|
"new_post_id": self.new_post_id,
|
|
"recycle_number": self.recycle_number,
|
|
"modifications": self.modifications,
|
|
"modification_notes": self.modification_notes,
|
|
"original_engagement_rate": self.original_engagement_rate,
|
|
"new_engagement_rate": self.new_engagement_rate,
|
|
"status": self.status,
|
|
"reason": self.reason,
|
|
"recycled_at": self.recycled_at.isoformat() if self.recycled_at else None,
|
|
"scheduled_for": self.scheduled_for.isoformat() if self.scheduled_for else None
|
|
}
|