leads-optimizer/weekly_report.py
José Manuel Gómez a94c18c13c Add daily portfolio analysis and weekly strategic report
- agent.py: portfolio_daily_analysis() for daily Slack block,
  weekly_strategic_analysis() for deep weekly report
- run.py: call portfolio analysis before Slack send
- slack_reporter.py: add strategic diagnosis block at end of daily report
- weekly_report.py: standalone weekly report script
- .github/workflows/weekly.yml: runs Mondays at 9am (CEST)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 20:22:37 +02:00

135 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Informe estratégico semanal de Leads Optimizer.
Se ejecuta los lunes via GitHub Actions y envía un análisis profundo a Slack.
"""
import json
import sys
import os
import requests
from datetime import datetime, timedelta
import config
from airtable_client import AirtableClient
from google_ads_client import GoogleAdsClient
from agent import weekly_strategic_analysis
from analyzer import analyze
from slack_reporter import MESES_ES
MESES_ES_LOCAL = MESES_ES
def _get_week_days(offset_weeks: int = 0) -> list[str]:
"""Devuelve los 7 días YYYY-MM-DD de la semana offset_weeks atrás (0=esta, 1=anterior)."""
today = datetime.now().date()
monday = today - timedelta(days=today.weekday()) - timedelta(weeks=offset_weeks)
return [(monday + timedelta(days=i)).strftime("%Y-%m-%d") for i in range(7)]
def _filter_games_md_by_days(games_md: dict, days: list[str]) -> dict:
"""Filtra MetricasDiarias de GAMes a los días indicados (formato 'DD')."""
day_keys = {d[8:10] for d in days}
return {k: v for k, v in games_md.items() if k in day_keys}
def run_weekly():
now = datetime.now()
print(f"\n{'='*55}")
print(f" INFORME SEMANAL — {now.strftime('%d/%m/%Y %H:%M')}")
print(f"{'='*55}\n")
at = AirtableClient()
gads = GoogleAdsClient()
# Obtener MetricasDiarias de GAMes del mes en curso
mes, anio = now.month, now.year
print("Cargando GAMes...")
games_rid = at.get_or_create_games_record(mes, anio)
games_md = at.get_games_metricas(games_rid)
# Calcular semanas
this_week_days = _get_week_days(0)
prev_week_days = _get_week_days(1)
games_this = _filter_games_md_by_days(games_md, this_week_days)
games_prev = _filter_games_md_by_days(games_md, prev_week_days)
# Si la semana anterior cruza con el mes pasado, intentar cargar ese GAMes también
prev_month_days = [d for d in prev_week_days if d[:7] != now.strftime("%Y-%m")]
if prev_month_days:
prev_mes = mes - 1 if mes > 1 else 12
prev_anio = anio if mes > 1 else anio - 1
prev_records = at.games.all(formula=f"AND({{Mes}}='{prev_mes}',{{Año}}='{prev_anio}')")
if prev_records:
try:
prev_md = json.loads(prev_records[0]["fields"].get("MetricasDiarias") or "{}")
except Exception:
prev_md = {}
extra = _filter_games_md_by_days(prev_md, prev_month_days)
games_prev.update(extra)
# Cargar métricas de campañas activas para el análisis de portfolio
print("Cargando campañas activas...")
campaigns = at.get_active_gacampaignmes()
monthly_metrics = gads.get_monthly_metrics_all()
collected = []
for campaign in campaigns:
cid = campaign["google_campaign_id"]
metrics = monthly_metrics.get(cid, {"cost": 0, "conversions": 0, "clicks": 0,
"impressions": 0, "ctr": 0, "budget_daily": 0,
"status": "UNKNOWN"})
leads = campaign.get("conv_leads_lake_mes", 0)
analysis = analyze(campaign, leads, metrics)
collected.append({"campaign": campaign, "metrics": metrics,
"analysis": analysis, "leads": leads})
print("Generando análisis estratégico semanal con IA...")
mes_nombre = MESES_ES_LOCAL.get(mes, str(mes))
analysis_text = weekly_strategic_analysis(games_this, games_prev, collected, mes_nombre)
# Construir mensaje Slack
if not config.SLACK_WEBHOOK_URL:
print("SLACK_WEBHOOK_URL no configurada.")
print("\n--- ANÁLISIS ---\n")
print(analysis_text)
return
this_range = f"{this_week_days[0][8:10]}/{this_week_days[0][5:7]}{this_week_days[-1][8:10]}/{this_week_days[-1][5:7]}"
prev_range = f"{prev_week_days[0][8:10]}/{prev_week_days[0][5:7]}{prev_week_days[-1][8:10]}/{prev_week_days[-1][5:7]}"
blocks = [
{
"type": "header",
"text": {"type": "plain_text",
"text": f"INFORME SEMANAL — {now.strftime('%d/%m/%Y')}"},
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"_Semana actual: {this_range} | Semana anterior: {prev_range}_",
},
},
{"type": "divider"},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"🤖 *ANÁLISIS ESTRATÉGICO SEMANAL*\n{analysis_text}",
},
},
]
try:
resp = requests.post(config.SLACK_WEBHOOK_URL, json={"blocks": blocks}, timeout=10)
if resp.status_code != 200:
print(f"⚠️ Slack respondió {resp.status_code}: {resp.text[:200]}")
else:
print("✓ Informe semanal enviado a Slack.")
except Exception as e:
print(f"⚠️ Error enviando a Slack: {e}")
if __name__ == "__main__":
run_weekly()