Order campaign output by priority and add direct Google Ads links
- Campaigns now shown in three sections: 1. ACCIONES CRÍTICAS (urgencia PAUSAR/SPRINT) 2. ACCIONES NO CRÍTICAS (other budget changes) 3. SIN CAMBIOS (MANTENER) - First pass collects all data, then sorts before printing - Each campaign shows direct Google Ads link with account ID - Summary table also grouped by section with links Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b0f0ad394b
commit
7344cc0c52
112
run.py
112
run.py
@ -40,6 +40,42 @@ ICONOS = {
|
|||||||
"EN_RITMO": "✅",
|
"EN_RITMO": "✅",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ACCION_ICONOS = {
|
||||||
|
"PAUSAR": "⛔",
|
||||||
|
"AUMENTAR_PRESUPUESTO": "📈",
|
||||||
|
"REDUCIR_PRESUPUESTO": "📉",
|
||||||
|
"MANTENER": "✅",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Google Ads customer ID (sin guiones) para construir enlaces directos
|
||||||
|
_CUSTOMER_ID = config.GOOGLE_ADS_LOGIN_CUSTOMER_ID.replace("-", "")
|
||||||
|
|
||||||
|
|
||||||
|
def _ads_link(campaign_id: str) -> str:
|
||||||
|
return f"https://ads.google.com/aw/campaigns?campaignId={campaign_id}&__c={_CUSTOMER_ID}"
|
||||||
|
|
||||||
|
|
||||||
|
def _priority(item: dict) -> int:
|
||||||
|
"""
|
||||||
|
0 — Crítica: urgencia PAUSAR o SPRINT
|
||||||
|
1 — No crítica: accion cambia presupuesto pero urgencia no es crítica
|
||||||
|
2 — Mantener: accion MANTENER
|
||||||
|
"""
|
||||||
|
urgencia = item["analysis"]["urgencia"]
|
||||||
|
accion = item["decision"]["accion"]
|
||||||
|
if urgencia in ("PAUSAR", "SPRINT"):
|
||||||
|
return 0
|
||||||
|
if accion != "MANTENER":
|
||||||
|
return 1
|
||||||
|
return 2
|
||||||
|
|
||||||
|
|
||||||
|
SECCION_LABELS = {
|
||||||
|
0: "ACCIONES CRÍTICAS (PAUSAR / AUMENTAR)",
|
||||||
|
1: "ACCIONES NO CRÍTICAS",
|
||||||
|
2: "SIN CAMBIOS (MANTENER)",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
print(f"\n{'='*55}")
|
print(f"\n{'='*55}")
|
||||||
@ -71,30 +107,70 @@ def run():
|
|||||||
print()
|
print()
|
||||||
|
|
||||||
campaigns = at.get_active_campaigns()
|
campaigns = at.get_active_campaigns()
|
||||||
print(f"→ {len(campaigns)} campañas activas encontradas\n")
|
print(f"→ {len(campaigns)} campañas activas encontradas")
|
||||||
|
print("→ Analizando...\n")
|
||||||
|
|
||||||
resumen = []
|
# === PRIMERA PASADA: recopilar datos de todas las campañas ===
|
||||||
|
collected = []
|
||||||
|
skipped = []
|
||||||
|
|
||||||
for campaign in campaigns:
|
for campaign in campaigns:
|
||||||
cid = campaign["google_campaign_id"]
|
cid = campaign["google_campaign_id"]
|
||||||
print(f"{'─'*55}")
|
|
||||||
print(f"📚 {campaign['curso']}")
|
|
||||||
print(f" Campaign ID: {cid} | PPL: {campaign['ppl']}€ | Cap: {campaign['capping_mensual']} leads")
|
|
||||||
|
|
||||||
# 1. Leads reales desde Airtable + vincular en campo Leads Lake
|
|
||||||
leads, lead_ids = at.get_leads_this_month(campaign["cursoid_text"])
|
leads, lead_ids = at.get_leads_this_month(campaign["cursoid_text"])
|
||||||
at.update_campaign_leads_lake(campaign["airtable_id"], lead_ids)
|
at.update_campaign_leads_lake(campaign["airtable_id"], lead_ids)
|
||||||
|
|
||||||
# 2. Métricas de Google Ads
|
|
||||||
metrics = gads.get_campaign_metrics(cid)
|
metrics = gads.get_campaign_metrics(cid)
|
||||||
if not metrics:
|
if not metrics:
|
||||||
print(f" ⚠️ Sin métricas en Google Ads, omitiendo.\n")
|
skipped.append(f"[{cid}] {campaign['curso']}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 3. Análisis
|
|
||||||
analysis = analyze(campaign, leads, metrics)
|
analysis = analyze(campaign, leads, metrics)
|
||||||
icono = ICONOS.get(analysis["urgencia"], "❓")
|
decision = decide(analysis)
|
||||||
|
|
||||||
|
collected.append({
|
||||||
|
"campaign": campaign,
|
||||||
|
"leads": leads,
|
||||||
|
"metrics": metrics,
|
||||||
|
"analysis": analysis,
|
||||||
|
"decision": decision,
|
||||||
|
})
|
||||||
|
|
||||||
|
if skipped:
|
||||||
|
print(f" ⚠️ {len(skipped)} campañas sin métricas omitidas:")
|
||||||
|
for s in skipped:
|
||||||
|
print(f" · {s}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Ordenar: 0=críticas → 1=no críticas → 2=mantener
|
||||||
|
collected.sort(key=_priority)
|
||||||
|
|
||||||
|
# === SEGUNDA PASADA: imprimir en orden + aplicar decisiones ===
|
||||||
|
resumen = []
|
||||||
|
last_priority = -1
|
||||||
|
|
||||||
|
for item in collected:
|
||||||
|
campaign = item["campaign"]
|
||||||
|
leads = item["leads"]
|
||||||
|
metrics = item["metrics"]
|
||||||
|
analysis = item["analysis"]
|
||||||
|
decision = item["decision"]
|
||||||
|
cid = campaign["google_campaign_id"]
|
||||||
|
|
||||||
|
p = _priority(item)
|
||||||
|
if p != last_priority:
|
||||||
|
print(f"\n{'━'*55}")
|
||||||
|
print(f" {SECCION_LABELS[p]}")
|
||||||
|
print(f"{'━'*55}")
|
||||||
|
last_priority = p
|
||||||
|
|
||||||
|
icono = ICONOS.get(analysis["urgencia"], "❓")
|
||||||
|
accion_icono = ACCION_ICONOS.get(decision["accion"], "")
|
||||||
|
|
||||||
|
print(f"{'─'*55}")
|
||||||
|
print(f"📚 {campaign['curso']}")
|
||||||
|
print(f" ID: {cid} | PPL: {campaign['ppl']}€ | Cap: {campaign['capping_mensual']} leads")
|
||||||
|
print(f" 🔗 {_ads_link(cid)}")
|
||||||
print(f" Leads mes: {leads}/{campaign['capping_mensual']} "
|
print(f" Leads mes: {leads}/{campaign['capping_mensual']} "
|
||||||
f"({analysis['ratio_leads']*100:.0f}% cap) | "
|
f"({analysis['ratio_leads']*100:.0f}% cap) | "
|
||||||
f"Ratio mes: {analysis['ratio_mes']*100:.0f}%")
|
f"Ratio mes: {analysis['ratio_mes']*100:.0f}%")
|
||||||
@ -108,9 +184,7 @@ def run():
|
|||||||
print(f" 🚨 ALERTA TRACKING: {analysis['discrepancia_tracking']} leads de diferencia "
|
print(f" 🚨 ALERTA TRACKING: {analysis['discrepancia_tracking']} leads de diferencia "
|
||||||
f"entre Airtable ({leads}) y Google Ads ({int(analysis['conversiones_google'])})")
|
f"entre Airtable ({leads}) y Google Ads ({int(analysis['conversiones_google'])})")
|
||||||
|
|
||||||
# 4. Decisión del agente
|
print(f" Decisión: {accion_icono} {decision['accion']} "
|
||||||
decision = decide(analysis)
|
|
||||||
print(f" Decisión: {decision['accion']} "
|
|
||||||
f"(confianza: {decision['confianza']*100:.0f}%)")
|
f"(confianza: {decision['confianza']*100:.0f}%)")
|
||||||
print(f" Justificación: {decision['justificacion']}")
|
print(f" Justificación: {decision['justificacion']}")
|
||||||
|
|
||||||
@ -120,7 +194,6 @@ def run():
|
|||||||
if decision.get("alerta"):
|
if decision.get("alerta"):
|
||||||
print(f" 🚨 {decision['alerta']}")
|
print(f" 🚨 {decision['alerta']}")
|
||||||
|
|
||||||
# 5. Aplicar
|
|
||||||
apply_decision(campaign, decision, metrics, gads)
|
apply_decision(campaign, decision, metrics, gads)
|
||||||
|
|
||||||
resumen.append({
|
resumen.append({
|
||||||
@ -131,18 +204,25 @@ def run():
|
|||||||
"cpa": analysis["cpa_actual"],
|
"cpa": analysis["cpa_actual"],
|
||||||
"margen": f"{analysis['margen']*100:.0f}%",
|
"margen": f"{analysis['margen']*100:.0f}%",
|
||||||
"consejo": decision.get("consejo", ""),
|
"consejo": decision.get("consejo", ""),
|
||||||
|
"link": _ads_link(cid),
|
||||||
|
"prioridad": p,
|
||||||
})
|
})
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Resumen final
|
# Resumen final ordenado por prioridad (ya está ordenado)
|
||||||
print(f"{'='*55}")
|
print(f"{'='*55}")
|
||||||
print("RESUMEN FINAL")
|
print("RESUMEN FINAL")
|
||||||
print(f"{'='*55}")
|
print(f"{'='*55}")
|
||||||
|
last_p = -1
|
||||||
for r in resumen:
|
for r in resumen:
|
||||||
|
if r["prioridad"] != last_p:
|
||||||
|
print(f"\n --- {SECCION_LABELS[r['prioridad']]} ---")
|
||||||
|
last_p = r["prioridad"]
|
||||||
print(f" {r['curso'][:35]:<35} | {r['urgencia']:<12} | {r['accion']:<25} | {r['leads']} leads | {r['margen']} margen")
|
print(f" {r['curso'][:35]:<35} | {r['urgencia']:<12} | {r['accion']:<25} | {r['leads']} leads | {r['margen']} margen")
|
||||||
|
print(f" {'':35} {r['link']}")
|
||||||
if r["consejo"]:
|
if r["consejo"]:
|
||||||
print(f" {'':35} {'💡':>14} {r['consejo']}")
|
print(f" {'':35} 💡 {r['consejo']}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user