Fix Slack block limit: send one message per campaign + error logging
- Refactor send_daily_report: summary in one message, each campaign in its own message — eliminates the 50-block Slack limit that was silently dropping campaigns - _post now raises RuntimeError on Slack API errors (was silently returning None) - send_slack_report.py accepts optional date argument (defaults to yesterday) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6ec3342661
commit
ac7a0a9738
@ -17,7 +17,8 @@ def _extract_vertical(name: str) -> str:
|
||||
|
||||
|
||||
def main():
|
||||
run_date = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
import sys as _sys
|
||||
run_date = _sys.argv[1] if len(_sys.argv) > 1 else (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
print(f"Reenviando informe para {run_date}...")
|
||||
|
||||
meta = MetaAdsClient()
|
||||
|
||||
@ -57,7 +57,10 @@ def _post(method: str, **payload) -> dict:
|
||||
json=payload,
|
||||
timeout=10,
|
||||
)
|
||||
return resp.json()
|
||||
data = resp.json()
|
||||
if not data.get("ok"):
|
||||
raise RuntimeError(f"Slack {method} error: {data.get('error')} — {data.get('response_metadata', {}).get('messages', '')}")
|
||||
return data
|
||||
|
||||
|
||||
def update_message(channel: str, ts: str, text: str):
|
||||
@ -253,15 +256,23 @@ def send_daily_report(
|
||||
})
|
||||
|
||||
blocks.append({"type": "divider"})
|
||||
|
||||
# ── Análisis por campaña: decisión + adsets + anuncios ────────────────────
|
||||
if campaign_details:
|
||||
blocks.append({
|
||||
"type": "section",
|
||||
"text": {"type": "mrkdwn",
|
||||
"text": f"*Análisis por campaña ({campaigns_analyzed} activas ayer)*"},
|
||||
"type": "context",
|
||||
"elements": [{"type": "mrkdwn",
|
||||
"text": f"{campaigns_analyzed} campañas analizadas — detalle por campaña a continuación"}],
|
||||
})
|
||||
# Build lookup: campaign_name → action info
|
||||
|
||||
# ── Enviar resumen ────────────────────────────────────────────────────────
|
||||
result = _post(
|
||||
"chat.postMessage",
|
||||
channel=config.SLACK_CHANNEL_ID,
|
||||
blocks=blocks,
|
||||
text=f"Meta Optimizer — {prefix} — {date_label}",
|
||||
)
|
||||
ts = result.get("ts")
|
||||
|
||||
# ── Un mensaje por campaña (sin límite de bloques) ────────────────────────
|
||||
if campaign_details:
|
||||
action_map = {a["campaign_name"]: a for a in actions}
|
||||
|
||||
for cid, detail in campaign_details.items():
|
||||
@ -279,11 +290,9 @@ def send_daily_report(
|
||||
budget = bid_cfg.get("daily_budget_eur")
|
||||
budget_str = f"{budget:.0f}€/día" if budget else "—"
|
||||
|
||||
# Icono y etiqueta de acción
|
||||
atype = action["action_type"] if action else "MAINTAIN"
|
||||
emoji, alabel = _ACTION_DISPLAY.get(atype, ("⚪", atype))
|
||||
|
||||
# Campaign header
|
||||
header_text = (
|
||||
f"{emoji} *{name}*\n"
|
||||
f"Vertical: _{vertical}_ | Margen: *{m_str}* | "
|
||||
@ -291,25 +300,23 @@ def send_daily_report(
|
||||
f"*Decisión: {alabel}*"
|
||||
)
|
||||
if action:
|
||||
justification = action.get("justification", "")[:500]
|
||||
header_text += f"\n_{justification}_"
|
||||
header_text += f"\n_{action.get('justification', '')[:500]}_"
|
||||
if action.get("alert"):
|
||||
header_text += f"\n:warning: {action['alert']}"
|
||||
|
||||
blocks.append({
|
||||
"type": "section",
|
||||
"text": {"type": "mrkdwn", "text": header_text},
|
||||
})
|
||||
camp_blocks: list = [
|
||||
{"type": "section", "text": {"type": "mrkdwn", "text": header_text}},
|
||||
]
|
||||
|
||||
# Botones solo para acciones que ejecutan algo en Meta API
|
||||
# Botones de acción a nivel campaña
|
||||
if action and atype in _ACTIONABLE:
|
||||
effect = _effect_text(action, budget)
|
||||
if effect:
|
||||
blocks.append({
|
||||
camp_blocks.append({
|
||||
"type": "section",
|
||||
"text": {"type": "mrkdwn", "text": effect},
|
||||
})
|
||||
blocks.append({
|
||||
camp_blocks.append({
|
||||
"type": "actions",
|
||||
"elements": [
|
||||
{
|
||||
@ -330,48 +337,26 @@ def send_daily_report(
|
||||
})
|
||||
|
||||
# Tabla adsets + anuncios
|
||||
detail_parts = []
|
||||
adset_table = _adset_ad_table(adsets, "Conjuntos de anuncios", show_bid=True)
|
||||
ad_table = _adset_ad_table(ads, "Anuncios")
|
||||
if adset_table:
|
||||
detail_parts.append(adset_table)
|
||||
if ad_table:
|
||||
detail_parts.append(ad_table)
|
||||
if detail_parts:
|
||||
combined = "\n\n".join(detail_parts)
|
||||
combined = "\n\n".join(filter(None, [adset_table, ad_table]))
|
||||
for chunk in [combined[i:i+2900] for i in range(0, len(combined), 2900)]:
|
||||
blocks.append({
|
||||
camp_blocks.append({
|
||||
"type": "section",
|
||||
"text": {"type": "mrkdwn", "text": chunk},
|
||||
})
|
||||
|
||||
# Botones de pausa por anuncio (solo los que Claude recomienda pausar)
|
||||
blocks.extend(_ad_action_blocks(ads))
|
||||
# Botones de pausa por anuncio
|
||||
camp_blocks.extend(_ad_action_blocks(ads))
|
||||
|
||||
blocks.append({"type": "divider"})
|
||||
|
||||
if len(blocks) >= 45: # Safety margin before Slack's 50-block limit
|
||||
blocks.append({
|
||||
"type": "section",
|
||||
"text": {"type": "mrkdwn",
|
||||
"text": "_... más campañas disponibles en Baserow._"},
|
||||
})
|
||||
break
|
||||
|
||||
# ── Pie ───────────────────────────────────────────────────────────────────
|
||||
blocks.append({
|
||||
"type": "context",
|
||||
"elements": [{"type": "mrkdwn",
|
||||
"text": f"{campaigns_analyzed} campañas analizadas ayer"}],
|
||||
})
|
||||
|
||||
result = _post(
|
||||
_post(
|
||||
"chat.postMessage",
|
||||
channel=config.SLACK_CHANNEL_ID,
|
||||
blocks=blocks,
|
||||
text=f"Meta Optimizer — {prefix} — {date_label}",
|
||||
blocks=camp_blocks,
|
||||
text=name,
|
||||
)
|
||||
return result.get("ts") if result.get("ok") else None
|
||||
|
||||
return ts
|
||||
|
||||
|
||||
def send_execution_summary(log: dict):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user