- 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>
135 lines
4.9 KiB
Python
135 lines
4.9 KiB
Python
"""
|
||
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()
|