Major improvements to AI content generation: ## New Components (app/services/ai/) - PromptLibrary: YAML-based prompt templates with inheritance - ContextEngine: Anti-repetition and best performers tracking - ContentGeneratorV2: Enhanced generation with dynamic parameters - PlatformAdapter: Platform-specific content adaptation - ContentValidator: AI-powered quality scoring (0-100) ## Prompt Library (app/prompts/) - 3 personalities: default, educational, promotional - 5 templates: tip_tech, product_post, service_post, thread, response - 4 platform configs: x, threads, instagram, facebook - Few-shot examples by category: ia, productividad, seguridad ## Database Changes - New table: content_memory (tracks generated content) - New columns in posts: quality_score, score_breakdown, generation_attempts ## New API Endpoints (/api/v2/generate/) - POST /generate - Generation with quality check - POST /generate/batch - Batch generation - POST /quality/evaluate - Evaluate content quality - GET /templates, /personalities, /platforms - List configs ## Celery Tasks - update_engagement_scores (every 6h) - cleanup_old_memory (monthly) - refresh_best_posts_yaml (weekly) ## Tests - Comprehensive tests for all AI engine components Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
159 lines
5.7 KiB
Python
159 lines
5.7 KiB
Python
"""
|
|
Modelo de Post - Posts generados y programados.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey, JSON, Enum
|
|
from sqlalchemy.dialects.postgresql import ARRAY
|
|
from sqlalchemy.orm import relationship
|
|
import enum
|
|
|
|
from app.core.database import Base
|
|
|
|
|
|
class PostStatus(enum.Enum):
|
|
"""Estados posibles de un post."""
|
|
DRAFT = "draft"
|
|
PENDING_APPROVAL = "pending_approval"
|
|
APPROVED = "approved"
|
|
SCHEDULED = "scheduled"
|
|
PUBLISHING = "publishing"
|
|
PUBLISHED = "published"
|
|
FAILED = "failed"
|
|
CANCELLED = "cancelled"
|
|
|
|
|
|
class ContentType(enum.Enum):
|
|
"""Tipos de contenido."""
|
|
TIP_TECH = "tip_tech"
|
|
DATO_CURIOSO = "dato_curioso"
|
|
FRASE_MOTIVACIONAL = "frase_motivacional"
|
|
EFEMERIDE = "efemeride"
|
|
PRODUCTO = "producto"
|
|
SERVICIO = "servicio"
|
|
HILO_EDUCATIVO = "hilo_educativo"
|
|
CASO_EXITO = "caso_exito"
|
|
PROMOCION = "promocion"
|
|
ANUNCIO = "anuncio"
|
|
MANUAL = "manual"
|
|
|
|
|
|
class Post(Base):
|
|
"""Modelo para posts de redes sociales."""
|
|
|
|
__tablename__ = "posts"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
|
|
# Contenido
|
|
content = Column(Text, nullable=False)
|
|
content_type = Column(String(50), nullable=False, index=True)
|
|
|
|
# Contenido adaptado por plataforma (opcional)
|
|
content_x = Column(Text, nullable=True) # Versión para X (280 chars)
|
|
content_threads = Column(Text, nullable=True)
|
|
content_instagram = Column(Text, nullable=True)
|
|
content_facebook = Column(Text, nullable=True)
|
|
|
|
# Plataformas destino
|
|
platforms = Column(ARRAY(String), nullable=False)
|
|
# Ejemplo: ["x", "threads", "instagram", "facebook"]
|
|
|
|
# Estado y programación
|
|
status = Column(String(50), default="draft", index=True)
|
|
scheduled_at = Column(DateTime, nullable=True, index=True)
|
|
published_at = Column(DateTime, nullable=True)
|
|
|
|
# Imagen
|
|
image_url = Column(String(500), nullable=True)
|
|
image_template_id = Column(Integer, ForeignKey("image_templates.id"), nullable=True)
|
|
|
|
# IDs de publicación en cada plataforma
|
|
platform_post_ids = Column(JSON, nullable=True)
|
|
# Ejemplo: {"x": "123456", "instagram": "789012", ...}
|
|
|
|
# Errores de publicación
|
|
error_message = Column(Text, nullable=True)
|
|
retry_count = Column(Integer, default=0)
|
|
|
|
# Aprobación
|
|
approval_required = Column(Boolean, default=False)
|
|
approved_by = Column(String(100), nullable=True)
|
|
approved_at = Column(DateTime, nullable=True)
|
|
|
|
# Relaciones con contenido fuente
|
|
product_id = Column(Integer, ForeignKey("products.id"), nullable=True)
|
|
service_id = Column(Integer, ForeignKey("services.id"), nullable=True)
|
|
tip_template_id = Column(Integer, ForeignKey("tip_templates.id"), nullable=True)
|
|
|
|
# Metadatos
|
|
hashtags = Column(ARRAY(String), nullable=True)
|
|
mentions = Column(ARRAY(String), nullable=True)
|
|
|
|
# Métricas (actualizadas después de publicar)
|
|
metrics = Column(JSON, nullable=True)
|
|
# Ejemplo: {"likes": 10, "retweets": 5, "comments": 3}
|
|
|
|
# A/B Testing
|
|
ab_test_id = Column(Integer, ForeignKey("ab_tests.id"), nullable=True, index=True)
|
|
|
|
# Recycling
|
|
is_recyclable = Column(Boolean, default=True)
|
|
recycled_from_id = Column(Integer, ForeignKey("posts.id"), nullable=True)
|
|
recycle_count = Column(Integer, default=0) # Times this post has been recycled
|
|
|
|
# AI Generation Quality
|
|
quality_score = Column(Integer, nullable=True, index=True) # 0-100 score from validator
|
|
score_breakdown = Column(JSON, nullable=True) # Detailed scoring breakdown
|
|
generation_attempts = Column(Integer, default=1) # Times regenerated before acceptance
|
|
|
|
# Timestamps
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
# Relationships
|
|
metrics_history = relationship("PostMetrics", back_populates="post", cascade="all, delete-orphan")
|
|
|
|
def __repr__(self):
|
|
return f"<Post {self.id} - {self.status}>"
|
|
|
|
def to_dict(self):
|
|
"""Convertir a diccionario."""
|
|
return {
|
|
"id": self.id,
|
|
"content": self.content,
|
|
"content_type": self.content_type,
|
|
"content_x": self.content_x,
|
|
"content_threads": self.content_threads,
|
|
"content_instagram": self.content_instagram,
|
|
"content_facebook": self.content_facebook,
|
|
"platforms": self.platforms,
|
|
"status": self.status,
|
|
"scheduled_at": self.scheduled_at.isoformat() if self.scheduled_at else None,
|
|
"published_at": self.published_at.isoformat() if self.published_at else None,
|
|
"image_url": self.image_url,
|
|
"platform_post_ids": self.platform_post_ids,
|
|
"error_message": self.error_message,
|
|
"approval_required": self.approval_required,
|
|
"hashtags": self.hashtags,
|
|
"metrics": self.metrics,
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
"ab_test_id": self.ab_test_id,
|
|
"is_recyclable": self.is_recyclable,
|
|
"recycled_from_id": self.recycled_from_id,
|
|
"recycle_count": self.recycle_count,
|
|
"quality_score": self.quality_score,
|
|
"score_breakdown": self.score_breakdown,
|
|
"generation_attempts": self.generation_attempts
|
|
}
|
|
|
|
def get_content_for_platform(self, platform: str) -> str:
|
|
"""Obtener contenido adaptado para una plataforma específica."""
|
|
platform_content = {
|
|
"x": self.content_x,
|
|
"threads": self.content_threads,
|
|
"instagram": self.content_instagram,
|
|
"facebook": self.content_facebook
|
|
}
|
|
return platform_content.get(platform) or self.content
|