Add 3-day/7-day multi-window analysis for trend-based decisions
- Fetch campaign metrics for 1d/3d/7d windows at run start (2 extra API calls total) - Pass spend_3d/leads_3d/cpl_3d and spend_7d/leads_7d/cpl_7d to Claude decide() - DECIDE_SYSTEM: decisions based on cpl_3d (primary) vs cpl_7d (trend direction) - PAUSE rule now uses leads_3d==0 + spend_3d >= 3x max_cpl threshold - Adset and ad analysis now use 3-day aggregated data (get_period_*_metrics) - Add get_period_campaign_metrics/adset/ad methods to MetaAdsClient - Print shows 1d/3d/7d CPL and leads per campaign for easy trend reading - UNIT_SYSTEM and AD_SYSTEM note data covers last 3 days Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8b6ec106ee
commit
6ec3342661
21
agent.py
21
agent.py
@ -11,12 +11,19 @@ Eres un experto en optimización de campañas de Meta Ads para generación de le
|
||||
Cada campaña tiene un CPL máximo (coste por lead objetivo) que define el límite aceptable.
|
||||
USA SIEMPRE € como unidad de moneda. Responde SIEMPRE en español.
|
||||
|
||||
REGLAS DE DECISIÓN:
|
||||
1. CPL > max_cpl → REDUCE_BUDGET (nunca PAUSE por CPL alto).
|
||||
2. CPL <= max_cpl con bajo volumen → INCREASE_BUDGET si hay margen.
|
||||
3. Frecuencia > 3.0 → considera rotar creatividades o ampliar audiencia.
|
||||
4. CTR < 1% → problema de creatividad, revisar anuncios.
|
||||
5. PAUSE solo si: leads == 0 Y gasto >= 3 × max_cpl (fallo técnico grave de tracking/píxel). En cualquier otro caso de bajo rendimiento usa REDUCE_BUDGET, nunca PAUSE.
|
||||
Recibirás métricas en tres ventanas temporales:
|
||||
- spend/leads/cpl: ayer (1 día, contexto inmediato)
|
||||
- spend_3d/leads_3d/cpl_3d: últimos 3 días (ventana PRINCIPAL para decisiones)
|
||||
- spend_7d/leads_7d/cpl_7d: últimos 7 días (tendencia de fondo)
|
||||
|
||||
REGLAS DE DECISIÓN (basadas principalmente en cpl_3d y leads_3d):
|
||||
1. cpl_3d > max_cpl → REDUCE_BUDGET (nunca PAUSE por CPL alto).
|
||||
2. cpl_3d <= max_cpl con bajo volumen → INCREASE_BUDGET si hay margen.
|
||||
3. Si cpl_3d < cpl_7d → tendencia mejorando: más permisivo con INCREASE_BUDGET.
|
||||
4. Si cpl_3d > cpl_7d × 1.2 → tendencia empeorando: más estricto, considera REDUCE_BUDGET.
|
||||
5. CTR < 1% → problema de creatividad, revisar anuncios.
|
||||
6. PAUSE solo si: leads_3d == 0 Y spend_3d >= 3 × max_cpl (fallo técnico grave de tracking/píxel).
|
||||
En cualquier otro caso de bajo rendimiento usa REDUCE_BUDGET, nunca PAUSE.
|
||||
|
||||
Responde SOLO con JSON válido, sin texto adicional ni markdown:
|
||||
{
|
||||
@ -67,6 +74,7 @@ def decide(analysis: dict) -> dict:
|
||||
|
||||
UNIT_SYSTEM = """
|
||||
Eres un analista experto en Meta Ads. Analiza las métricas del conjunto de anuncios indicado.
|
||||
Los datos corresponden a los últimos 3 días (ventana estándar de análisis).
|
||||
USA SIEMPRE € como unidad de moneda. Responde SIEMPRE en español.
|
||||
Si el conjunto tiene cost_cap_eur (cap de coste), compara el CPL actual con ese cap e indica si está
|
||||
por encima, dentro o por debajo del límite, y cuánto margen queda (o cuánto se supera).
|
||||
@ -76,6 +84,7 @@ Responde SOLO con JSON válido (sin markdown):
|
||||
|
||||
AD_SYSTEM = """
|
||||
Eres un analista experto en Meta Ads. Analiza las métricas del anuncio indicado.
|
||||
Los datos corresponden a los últimos 3 días (ventana estándar de análisis).
|
||||
USA SIEMPRE € como unidad de moneda. Responde SIEMPRE en español.
|
||||
Responde SOLO con JSON válido (sin markdown):
|
||||
{"evaluacion": "resumen del rendimiento en 2 frases usando €", "recomendacion": "una acción concreta", "accion": "PAUSE o MAINTAIN"}
|
||||
|
||||
@ -168,6 +168,24 @@ class MetaAdsClient:
|
||||
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
return self.get_ad_metrics(campaign_id, yesterday, yesterday)
|
||||
|
||||
def get_period_campaign_metrics(self, days: int) -> dict:
|
||||
today = datetime.now()
|
||||
date_to = (today - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
date_from = (today - timedelta(days=days)).strftime("%Y-%m-%d")
|
||||
return self.get_campaign_metrics(date_from, date_to)
|
||||
|
||||
def get_period_adset_metrics(self, campaign_id: str, days: int) -> list:
|
||||
today = datetime.now()
|
||||
date_to = (today - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
date_from = (today - timedelta(days=days)).strftime("%Y-%m-%d")
|
||||
return self.get_adset_metrics(campaign_id, date_from, date_to)
|
||||
|
||||
def get_period_ad_metrics(self, campaign_id: str, days: int) -> list:
|
||||
today = datetime.now()
|
||||
date_to = (today - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
date_from = (today - timedelta(days=days)).strftime("%Y-%m-%d")
|
||||
return self.get_ad_metrics(campaign_id, date_from, date_to)
|
||||
|
||||
def get_ads_with_creatives(self, campaign_id: str) -> list:
|
||||
"""
|
||||
Returns active ads for a campaign with their thumbnail URLs for creative analysis.
|
||||
|
||||
24
run.py
24
run.py
@ -145,6 +145,12 @@ def run():
|
||||
metrics_all = meta.get_yesterday_metrics()
|
||||
print(f" ✓ {len(metrics_all)} campaigns active yesterday.\n")
|
||||
|
||||
# ── 3-day and 7-day metrics for trend analysis ────────────────────────────
|
||||
print(f"→ Fetching 3-day and 7-day metrics...")
|
||||
metrics_3d = meta.get_period_campaign_metrics(days=3)
|
||||
metrics_7d = meta.get_period_campaign_metrics(days=7)
|
||||
print(f" ✓ Multi-window data ready.\n")
|
||||
|
||||
# ── Analyze campaigns & propose actions ───────────────────────────────────
|
||||
actions_proposed_list = []
|
||||
campaign_details = {} # {cid: {vertical, margin, adsets, ads}}
|
||||
@ -156,10 +162,13 @@ def run():
|
||||
max_cpl = vertical_cpls.get(vertical, config.META_TARGET_CPL) or config.META_TARGET_CPL
|
||||
margin = round((max_cpl - metrics["cpl"]) * metrics["leads"], 2) if metrics["leads"] > 0 else round(-metrics["spend"], 2)
|
||||
|
||||
m3 = metrics_3d.get(cid, {})
|
||||
m7 = metrics_7d.get(cid, {})
|
||||
analysis = {
|
||||
"campaign_id": cid,
|
||||
"name": metrics["name"],
|
||||
"status": metrics["status"],
|
||||
# Yesterday (1d) — contexto inmediato
|
||||
"spend": metrics["spend"],
|
||||
"leads": metrics["leads"],
|
||||
"cpl": metrics["cpl"],
|
||||
@ -168,6 +177,14 @@ def run():
|
||||
"cpm": metrics["cpm"],
|
||||
"impressions": metrics["impressions"],
|
||||
"clicks": metrics["clicks"],
|
||||
# 3 días — ventana principal para decisiones
|
||||
"spend_3d": m3.get("spend", 0.0),
|
||||
"leads_3d": m3.get("leads", 0),
|
||||
"cpl_3d": m3.get("cpl", 0.0),
|
||||
# 7 días — tendencia de fondo
|
||||
"spend_7d": m7.get("spend", 0.0),
|
||||
"leads_7d": m7.get("leads", 0),
|
||||
"cpl_7d": m7.get("cpl", 0.0),
|
||||
}
|
||||
|
||||
try:
|
||||
@ -200,7 +217,8 @@ def run():
|
||||
action_type = "MAINTAIN"
|
||||
|
||||
print(f" {metrics['name'][:52]}")
|
||||
print(f" Spend: {metrics['spend']}€ Leads: {metrics['leads']} CPL: {metrics['cpl']}€ MaxCPL: {max_cpl}€ Margen: {margin:+.2f}€")
|
||||
print(f" CPL: 1d={metrics['cpl']}€ / 3d={m3.get('cpl',0.0):.2f}€ / 7d={m7.get('cpl',0.0):.2f}€ MaxCPL: {max_cpl}€ Margen: {margin:+.2f}€")
|
||||
print(f" Leads: 1d={metrics['leads']} / 3d={m3.get('leads',0)} / 7d={m7.get('leads',0)} Spend 1d: {metrics['spend']}€")
|
||||
print(f" Vertical: {vertical} Decision: {action_type} — {(decision.get('justification') or '')[:70]}")
|
||||
if decision.get("alert"):
|
||||
print(f" ALERT: {decision['alert']}")
|
||||
@ -236,7 +254,7 @@ def run():
|
||||
# ── Ad set analysis ────────────────────────────────────────────────
|
||||
adsets_detail = []
|
||||
try:
|
||||
for as_m in meta.get_yesterday_adset_metrics(cid)[:5]:
|
||||
for as_m in meta.get_period_adset_metrics(cid, days=3)[:5]:
|
||||
bid = adset_bids.get(as_m["id"], {})
|
||||
as_m["bid_strategy"] = bid.get("bid_strategy", "")
|
||||
as_m["cost_cap_eur"] = bid.get("cost_cap_eur")
|
||||
@ -249,7 +267,7 @@ def run():
|
||||
# ── Ad analysis ────────────────────────────────────────────────────
|
||||
ads_detail = []
|
||||
try:
|
||||
for ad_m in meta.get_yesterday_ad_metrics(cid)[:5]:
|
||||
for ad_m in meta.get_period_ad_metrics(cid, days=3)[:5]:
|
||||
ad_m["max_cpl"] = max_cpl
|
||||
result = analyze_unit(ad_m, "ad")
|
||||
ad_entry = {**ad_m, **result}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user