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>
117 lines
4.8 KiB
Python
117 lines
4.8 KiB
Python
"""
|
|
Analytics Report Model - Aggregated analytics snapshots.
|
|
"""
|
|
|
|
from datetime import datetime, date
|
|
from sqlalchemy import Column, Integer, Float, String, DateTime, Date, JSON, Text
|
|
|
|
from app.core.database import Base
|
|
|
|
|
|
class AnalyticsReport(Base):
|
|
"""
|
|
Stores aggregated analytics reports (daily, weekly, monthly).
|
|
"""
|
|
__tablename__ = "analytics_reports"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
|
|
# Report type and period
|
|
report_type = Column(String(20), nullable=False, index=True) # daily, weekly, monthly
|
|
period_start = Column(Date, nullable=False, index=True)
|
|
period_end = Column(Date, nullable=False)
|
|
platform = Column(String(50), nullable=True) # null = all platforms
|
|
|
|
# Aggregated metrics
|
|
total_posts = Column(Integer, default=0)
|
|
total_impressions = Column(Integer, default=0)
|
|
total_reach = Column(Integer, default=0)
|
|
total_engagements = Column(Integer, default=0)
|
|
total_likes = Column(Integer, default=0)
|
|
total_comments = Column(Integer, default=0)
|
|
total_shares = Column(Integer, default=0)
|
|
|
|
# Calculated averages
|
|
avg_engagement_rate = Column(Float, default=0.0)
|
|
avg_impressions_per_post = Column(Float, default=0.0)
|
|
avg_engagements_per_post = Column(Float, default=0.0)
|
|
|
|
# Comparison with previous period
|
|
posts_change_pct = Column(Float, nullable=True)
|
|
engagement_change_pct = Column(Float, nullable=True)
|
|
impressions_change_pct = Column(Float, nullable=True)
|
|
|
|
# Top performing data (JSON)
|
|
top_posts = Column(JSON, nullable=True)
|
|
# [{"post_id": 1, "content": "...", "engagement_rate": 5.2, "platform": "x"}]
|
|
|
|
best_times = Column(JSON, nullable=True)
|
|
# [{"day": 1, "hour": 12, "avg_engagement": 4.5}]
|
|
|
|
content_performance = Column(JSON, nullable=True)
|
|
# {"tip": {"posts": 10, "avg_engagement": 3.2}, "product": {...}}
|
|
|
|
platform_breakdown = Column(JSON, nullable=True)
|
|
# {"x": {"posts": 20, "engagement": 150}, "threads": {...}}
|
|
|
|
# Report content for Telegram
|
|
summary_text = Column(Text, nullable=True)
|
|
|
|
# Metadata
|
|
generated_at = Column(DateTime, default=datetime.utcnow)
|
|
sent_to_telegram = Column(DateTime, nullable=True)
|
|
|
|
def to_dict(self):
|
|
return {
|
|
"id": self.id,
|
|
"report_type": self.report_type,
|
|
"period_start": self.period_start.isoformat() if self.period_start else None,
|
|
"period_end": self.period_end.isoformat() if self.period_end else None,
|
|
"platform": self.platform,
|
|
"total_posts": self.total_posts,
|
|
"total_impressions": self.total_impressions,
|
|
"total_reach": self.total_reach,
|
|
"total_engagements": self.total_engagements,
|
|
"avg_engagement_rate": round(self.avg_engagement_rate, 2),
|
|
"avg_impressions_per_post": round(self.avg_impressions_per_post, 1),
|
|
"posts_change_pct": round(self.posts_change_pct, 1) if self.posts_change_pct else None,
|
|
"engagement_change_pct": round(self.engagement_change_pct, 1) if self.engagement_change_pct else None,
|
|
"top_posts": self.top_posts,
|
|
"best_times": self.best_times,
|
|
"content_performance": self.content_performance,
|
|
"platform_breakdown": self.platform_breakdown,
|
|
"generated_at": self.generated_at.isoformat() if self.generated_at else None
|
|
}
|
|
|
|
def generate_telegram_summary(self) -> str:
|
|
"""Generate formatted summary for Telegram."""
|
|
lines = [
|
|
f"📊 *Reporte {self.report_type.title()}*",
|
|
f"📅 {self.period_start} - {self.period_end}",
|
|
"",
|
|
f"📝 Posts publicados: *{self.total_posts}*",
|
|
f"👁 Impresiones: *{self.total_impressions:,}*",
|
|
f"💬 Interacciones: *{self.total_engagements:,}*",
|
|
f"📈 Engagement rate: *{self.avg_engagement_rate:.2f}%*",
|
|
]
|
|
|
|
if self.engagement_change_pct is not None:
|
|
emoji = "📈" if self.engagement_change_pct > 0 else "📉"
|
|
lines.append(f"{emoji} vs anterior: *{self.engagement_change_pct:+.1f}%*")
|
|
|
|
if self.platform_breakdown:
|
|
lines.append("")
|
|
lines.append("*Por plataforma:*")
|
|
for platform, data in self.platform_breakdown.items():
|
|
lines.append(f" • {platform}: {data.get('posts', 0)} posts, {data.get('engagements', 0)} interacciones")
|
|
|
|
if self.top_posts and len(self.top_posts) > 0:
|
|
lines.append("")
|
|
lines.append("*Top 3 posts:*")
|
|
for i, post in enumerate(self.top_posts[:3], 1):
|
|
content = post.get('content', '')[:50] + "..." if len(post.get('content', '')) > 50 else post.get('content', '')
|
|
lines.append(f" {i}. {content} ({post.get('engagement_rate', 0):.1f}%)")
|
|
|
|
self.summary_text = "\n".join(lines)
|
|
return self.summary_text
|