Add sync detail and per-campaign advice
- Sync shows campaign ID, name and status for new campaigns, and field-level changes (before/after) for updated ones - Agent now returns a 'consejo' field with a specific actionable recommendation per campaign - Summary table includes leads, margin and consejo per campaign Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2f88eb119d
commit
e4362c6ad9
4
agent.py
4
agent.py
@ -29,12 +29,14 @@ Devuelve ÚNICAMENTE un JSON válido con esta estructura exacta, sin texto adici
|
|||||||
"accion": "PAUSAR | REDUCIR_PRESUPUESTO | AUMENTAR_PRESUPUESTO | MANTENER",
|
"accion": "PAUSAR | REDUCIR_PRESUPUESTO | AUMENTAR_PRESUPUESTO | MANTENER",
|
||||||
"parametro": 1.0,
|
"parametro": 1.0,
|
||||||
"nuevo_budget_diario": 0.0,
|
"nuevo_budget_diario": 0.0,
|
||||||
"justificacion": "explicación breve en español",
|
"justificacion": "explicación breve del porqué de la decisión",
|
||||||
|
"consejo": "acción concreta y específica que debería tomar el gestor (keywords, pujas, anuncios, configuración, etc.)",
|
||||||
"alerta": "texto si hay algo crítico, null si no hay",
|
"alerta": "texto si hay algo crítico, null si no hay",
|
||||||
"confianza": 0.0
|
"confianza": 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
El campo nuevo_budget_diario = budget_diario_actual × parametro (calcula tú el valor final).
|
El campo nuevo_budget_diario = budget_diario_actual × parametro (calcula tú el valor final).
|
||||||
|
El campo consejo debe ser accionable y específico: qué revisar, qué cambiar, qué hacer a continuación.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -43,10 +43,10 @@ class AirtableClient:
|
|||||||
if r["fields"].get("CampaignID")
|
if r["fields"].get("CampaignID")
|
||||||
}
|
}
|
||||||
|
|
||||||
created = []
|
created = [] # lista de dicts {name, id, status}
|
||||||
updated = []
|
updated = [] # lista de dicts {name, id, changes}
|
||||||
to_create = []
|
to_create = []
|
||||||
to_update = [] # lista de (record_id, changes, name)
|
to_update = [] # lista de (record_id, changes, name, gid, at_status)
|
||||||
|
|
||||||
for gc in google_campaigns:
|
for gc in google_campaigns:
|
||||||
gid = gc["id"]
|
gid = gc["id"]
|
||||||
@ -55,16 +55,25 @@ class AirtableClient:
|
|||||||
|
|
||||||
if at_record is None:
|
if at_record is None:
|
||||||
to_create.append({"Campaign Name": gc["name"], "CampaignID": gid, "Status": at_status})
|
to_create.append({"Campaign Name": gc["name"], "CampaignID": gid, "Status": at_status})
|
||||||
created.append(gc["name"])
|
created.append({"name": gc["name"], "id": gid, "status": at_status})
|
||||||
else:
|
else:
|
||||||
changes = {}
|
changes = {}
|
||||||
|
change_detail = {}
|
||||||
if at_record["fields"].get("Campaign Name") != gc["name"]:
|
if at_record["fields"].get("Campaign Name") != gc["name"]:
|
||||||
changes["Campaign Name"] = gc["name"]
|
changes["Campaign Name"] = gc["name"]
|
||||||
|
change_detail["nombre"] = {
|
||||||
|
"antes": at_record["fields"].get("Campaign Name"),
|
||||||
|
"ahora": gc["name"],
|
||||||
|
}
|
||||||
if at_record["fields"].get("Status") != at_status:
|
if at_record["fields"].get("Status") != at_status:
|
||||||
changes["Status"] = at_status
|
changes["Status"] = at_status
|
||||||
|
change_detail["status"] = {
|
||||||
|
"antes": at_record["fields"].get("Status"),
|
||||||
|
"ahora": at_status,
|
||||||
|
}
|
||||||
if changes:
|
if changes:
|
||||||
to_update.append((at_record["id"], changes, gc["name"]))
|
to_update.append((at_record["id"], changes, gc["name"]))
|
||||||
updated.append(gc["name"])
|
updated.append({"name": gc["name"], "id": gid, "changes": change_detail})
|
||||||
|
|
||||||
# Crear en lotes de 10
|
# Crear en lotes de 10
|
||||||
for i in range(0, len(to_create), 10):
|
for i in range(0, len(to_create), 10):
|
||||||
|
|||||||
19
run.py
19
run.py
@ -56,12 +56,13 @@ def run():
|
|||||||
sync_result = at.sync_campaigns_from_google_ads(google_campaigns)
|
sync_result = at.sync_campaigns_from_google_ads(google_campaigns)
|
||||||
if sync_result["created"]:
|
if sync_result["created"]:
|
||||||
print(f" ✅ Campañas nuevas importadas ({len(sync_result['created'])}):")
|
print(f" ✅ Campañas nuevas importadas ({len(sync_result['created'])}):")
|
||||||
for name in sync_result["created"]:
|
for c in sync_result["created"]:
|
||||||
print(f" + {name}")
|
print(f" + [{c['id']}] {c['name']} → {c['status']}")
|
||||||
if sync_result["updated"]:
|
if sync_result["updated"]:
|
||||||
print(f" 🔄 Campañas con nombre actualizado ({len(sync_result['updated'])}):")
|
print(f" 🔄 Campañas actualizadas ({len(sync_result['updated'])}):")
|
||||||
for name in sync_result["updated"]:
|
for c in sync_result["updated"]:
|
||||||
print(f" ~ {name}")
|
for field, val in c["changes"].items():
|
||||||
|
print(f" ~ [{c['id']}] {c['name']} | {field}: '{val['antes']}' → '{val['ahora']}'")
|
||||||
if not sync_result["created"] and not sync_result["updated"]:
|
if not sync_result["created"] and not sync_result["updated"]:
|
||||||
print(" ✓ Sin cambios en el catálogo.")
|
print(" ✓ Sin cambios en el catálogo.")
|
||||||
print()
|
print()
|
||||||
@ -109,6 +110,9 @@ def run():
|
|||||||
f"(confianza: {decision['confianza']*100:.0f}%)")
|
f"(confianza: {decision['confianza']*100:.0f}%)")
|
||||||
print(f" Justificación: {decision['justificacion']}")
|
print(f" Justificación: {decision['justificacion']}")
|
||||||
|
|
||||||
|
if decision.get("consejo"):
|
||||||
|
print(f" 💡 Consejo: {decision['consejo']}")
|
||||||
|
|
||||||
if decision.get("alerta"):
|
if decision.get("alerta"):
|
||||||
print(f" 🚨 {decision['alerta']}")
|
print(f" 🚨 {decision['alerta']}")
|
||||||
|
|
||||||
@ -122,6 +126,7 @@ def run():
|
|||||||
"leads": f"{leads}/{campaign['capping_mensual']}",
|
"leads": f"{leads}/{campaign['capping_mensual']}",
|
||||||
"cpa": analysis["cpa_actual"],
|
"cpa": analysis["cpa_actual"],
|
||||||
"margen": f"{analysis['margen']*100:.0f}%",
|
"margen": f"{analysis['margen']*100:.0f}%",
|
||||||
|
"consejo": decision.get("consejo", ""),
|
||||||
})
|
})
|
||||||
|
|
||||||
print()
|
print()
|
||||||
@ -131,7 +136,9 @@ def run():
|
|||||||
print("RESUMEN FINAL")
|
print("RESUMEN FINAL")
|
||||||
print(f"{'='*55}")
|
print(f"{'='*55}")
|
||||||
for r in resumen:
|
for r in resumen:
|
||||||
print(f" {r['curso'][:35]:<35} | {r['urgencia']:<12} | {r['accion']}")
|
print(f" {r['curso'][:35]:<35} | {r['urgencia']:<12} | {r['accion']:<25} | {r['leads']} leads | {r['margen']} margen")
|
||||||
|
if r["consejo"]:
|
||||||
|
print(f" {'':35} {'💡':>14} {r['consejo']}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user