Align field names with actual Airtable schema
- Campaigns: Status='Activa' filter (was Activa=TRUE), CPAMax (was CPAMaximo), add MargenObjetivo and CursoID Text per campaign - Leads: filter by attr_cursoid + creado (was GoogleCampaignID + Fecha) - analyzer: expose margen_objetivo and margen_ok per campaign - config: remove global MARGEN_MINIMO, now per-campaign via Airtable - sync: new campaigns created with Status='Pausada' https://claude.ai/code/session_01WEcdWPxGWZKk8FGwBRLGR2
This commit is contained in:
parent
d68f9f5659
commit
905ad3bcd2
@ -11,17 +11,19 @@ class AirtableClient:
|
|||||||
|
|
||||||
def get_active_campaigns(self) -> list[dict]:
|
def get_active_campaigns(self) -> list[dict]:
|
||||||
"""Lee todas las campañas activas desde 'Google Ads Campaigns'."""
|
"""Lee todas las campañas activas desde 'Google Ads Campaigns'."""
|
||||||
records = self.campaigns.all(formula="{Activa}=TRUE()")
|
records = self.campaigns.all(formula="{Status}='Activa'")
|
||||||
result = []
|
result = []
|
||||||
for r in records:
|
for r in records:
|
||||||
f = r["fields"]
|
f = r["fields"]
|
||||||
result.append({
|
result.append({
|
||||||
"airtable_id": r["id"],
|
"airtable_id": r["id"],
|
||||||
"curso": f.get("Campaign Name", "Sin nombre"),
|
"curso": f.get("Campaign Name", "Sin nombre"),
|
||||||
|
"cursoid_text": str(f.get("CursoID Text", "")).strip(),
|
||||||
"google_campaign_id": str(f.get("CampaignID", "")).strip(),
|
"google_campaign_id": str(f.get("CampaignID", "")).strip(),
|
||||||
"ppl": float(f.get("PPL", 0)),
|
"ppl": float(f.get("PPL", 0)),
|
||||||
"capping_mensual": int(f.get("CapTotalMes", 0)),
|
"capping_mensual": int(f.get("CapTotalMes", 0)),
|
||||||
"cpa_maximo": float(f.get("CPAMaximo", 0)),
|
"cpa_maximo": float(f.get("CPAMax", 0)),
|
||||||
|
"margen_objetivo": float(f.get("MargenObjetivo", 0)),
|
||||||
})
|
})
|
||||||
return [c for c in result if c["google_campaign_id"]] # descartar sin ID
|
return [c for c in result if c["google_campaign_id"]] # descartar sin ID
|
||||||
|
|
||||||
@ -50,7 +52,7 @@ class AirtableClient:
|
|||||||
self.campaigns.create({
|
self.campaigns.create({
|
||||||
"Campaign Name": gc["name"],
|
"Campaign Name": gc["name"],
|
||||||
"CampaignID": int(gid),
|
"CampaignID": int(gid),
|
||||||
"Activa": False,
|
"Status": "Pausada",
|
||||||
})
|
})
|
||||||
created.append(gc["name"])
|
created.append(gc["name"])
|
||||||
else:
|
else:
|
||||||
@ -63,18 +65,18 @@ class AirtableClient:
|
|||||||
|
|
||||||
return {"created": created, "updated": updated}
|
return {"created": created, "updated": updated}
|
||||||
|
|
||||||
def get_leads_this_month(self, google_campaign_id: str) -> int:
|
def get_leads_this_month(self, cursoid_text: str) -> int:
|
||||||
"""
|
"""
|
||||||
Cuenta todos los leads del mes actual para una campaña.
|
Cuenta todos los leads validados del mes actual para un curso.
|
||||||
Todos los leads en Airtable están validados, sin filtro de estado.
|
Filtra por attr_cursoid (que coincide con CursoID Text de la campaña).
|
||||||
"""
|
"""
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
mes_inicio = f"{now.year}-{now.month:02d}-01"
|
mes_inicio = f"{now.year}-{now.month:02d}-01"
|
||||||
|
|
||||||
formula = (
|
formula = (
|
||||||
f"AND("
|
f"AND("
|
||||||
f"{{GoogleCampaignID}}='{google_campaign_id}',"
|
f"{{attr_cursoid}}='{cursoid_text}',"
|
||||||
f"{{Fecha}}>='{mes_inicio}'"
|
f"{{creado}}>='{mes_inicio}'"
|
||||||
f")"
|
f")"
|
||||||
)
|
)
|
||||||
records = self.leads.all(formula=formula)
|
records = self.leads.all(formula=formula)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ def analyze(campaign_config: dict, leads_entregados: int, ads_metrics: dict) ->
|
|||||||
capping = campaign_config["capping_mensual"]
|
capping = campaign_config["capping_mensual"]
|
||||||
ppl = campaign_config["ppl"]
|
ppl = campaign_config["ppl"]
|
||||||
cpa_max = campaign_config["cpa_maximo"]
|
cpa_max = campaign_config["cpa_maximo"]
|
||||||
|
margen_objetivo = campaign_config.get("margen_objetivo", 0)
|
||||||
gasto = ads_metrics.get("cost", 0)
|
gasto = ads_metrics.get("cost", 0)
|
||||||
conversiones_google = ads_metrics.get("conversions", 0)
|
conversiones_google = ads_metrics.get("conversions", 0)
|
||||||
|
|
||||||
@ -42,6 +43,8 @@ def analyze(campaign_config: dict, leads_entregados: int, ads_metrics: dict) ->
|
|||||||
"campaign_id": campaign_config["google_campaign_id"],
|
"campaign_id": campaign_config["google_campaign_id"],
|
||||||
"ppl": ppl,
|
"ppl": ppl,
|
||||||
"cpa_maximo": cpa_max,
|
"cpa_maximo": cpa_max,
|
||||||
|
"margen_objetivo": margen_objetivo,
|
||||||
|
"margen_ok": margen >= margen_objetivo if margen_objetivo > 0 else True,
|
||||||
"capping": capping,
|
"capping": capping,
|
||||||
"leads_entregados": leads_entregados,
|
"leads_entregados": leads_entregados,
|
||||||
"leads_restantes": leads_restantes,
|
"leads_restantes": leads_restantes,
|
||||||
|
|||||||
@ -20,5 +20,4 @@ GOOGLE_ADS_LOGIN_CUSTOMER_ID = os.environ["GOOGLE_ADS_LOGIN_CUSTOMER_ID"]
|
|||||||
ANTHROPIC_API_KEY = os.environ["ANTHROPIC_API_KEY"]
|
ANTHROPIC_API_KEY = os.environ["ANTHROPIC_API_KEY"]
|
||||||
|
|
||||||
# Operación
|
# Operación
|
||||||
DRY_RUN = True # True = solo sugiere, no aplica cambios en Google Ads
|
DRY_RUN = True # True = solo sugiere, no aplica cambios en Google Ads
|
||||||
MARGEN_MINIMO = 0.40 # 40% mínimo de margen sobre PPL
|
|
||||||
|
|||||||
2
run.py
2
run.py
@ -53,7 +53,7 @@ def run():
|
|||||||
print(f" Campaign ID: {cid} | PPL: {campaign['ppl']}€ | Cap: {campaign['capping_mensual']} leads")
|
print(f" Campaign ID: {cid} | PPL: {campaign['ppl']}€ | Cap: {campaign['capping_mensual']} leads")
|
||||||
|
|
||||||
# 1. Leads reales desde Airtable
|
# 1. Leads reales desde Airtable
|
||||||
leads = at.get_leads_this_month(cid)
|
leads = at.get_leads_this_month(campaign["cursoid_text"])
|
||||||
|
|
||||||
# 2. Métricas de Google Ads
|
# 2. Métricas de Google Ads
|
||||||
metrics = gads.get_campaign_metrics(cid)
|
metrics = gads.get_campaign_metrics(cid)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user