Add daily metrics snapshot (MetricasDiarias) to GACampaignMes
Each optimizer run writes yesterday's cost, revenue (conversions x PPL) and margin per campaign as a JSON entry keyed by day number into the MetricasDiarias field. Yesterday is used because Google Ads data for the current day is not yet available. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d58448f698
commit
45e9515ae8
@ -1,5 +1,6 @@
|
||||
from pyairtable import Api
|
||||
from datetime import datetime
|
||||
import json
|
||||
import config
|
||||
|
||||
|
||||
@ -307,6 +308,7 @@ class AirtableClient:
|
||||
"cpa_maximo": float(f.get("CPAMax") or 0),
|
||||
"margen_objetivo": float(f.get("MargenObjetivo") or 0),
|
||||
"conv_leads_lake_mes": int(f.get("ConvLeadsLakeMes") or 0),
|
||||
"metricas_diarias": f.get("MetricasDiarias") or "{}",
|
||||
})
|
||||
return result
|
||||
|
||||
@ -377,6 +379,18 @@ class AirtableClient:
|
||||
for i in range(0, len(batch), 10):
|
||||
self.gacampaignmes.batch_update(batch[i:i+10])
|
||||
|
||||
def batch_update_metricas_diarias(self, updates: list[dict]) -> None:
|
||||
"""
|
||||
Actualiza MetricasDiarias en GACampaignMes.
|
||||
updates: lista de {airtable_id, metricas_json (str)}
|
||||
"""
|
||||
batch = [
|
||||
{"id": u["airtable_id"], "fields": {"MetricasDiarias": u["metricas_json"]}}
|
||||
for u in updates
|
||||
]
|
||||
for i in range(0, len(batch), 10):
|
||||
self.gacampaignmes.batch_update(batch[i:i+10])
|
||||
|
||||
def batch_update_gacampaignmes_advice(self, updates: list[tuple]) -> None:
|
||||
"""
|
||||
Actualiza en lote los campos 'Consejo', 'Criticidad' y 'Log' de GACampaignMes.
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from google.ads.googleads.client import GoogleAdsClient as GAdsClient
|
||||
from google.ads.googleads.errors import GoogleAdsException
|
||||
from datetime import datetime
|
||||
import config
|
||||
|
||||
|
||||
@ -69,6 +70,36 @@ class GoogleAdsClient:
|
||||
print(f" ❌ Error obteniendo métricas mensuales: {e}")
|
||||
return result
|
||||
|
||||
def get_yesterday_metrics_all(self) -> dict:
|
||||
"""
|
||||
Devuelve métricas del día anterior para TODAS las campañas en una sola query.
|
||||
Retorna dict {campaign_id: {conversions, cost}}.
|
||||
"""
|
||||
from datetime import timedelta
|
||||
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
ga_service = self.client.get_service("GoogleAdsService")
|
||||
query = f"""
|
||||
SELECT
|
||||
campaign.id,
|
||||
metrics.conversions,
|
||||
metrics.cost_micros
|
||||
FROM campaign
|
||||
WHERE campaign.status != 'REMOVED'
|
||||
AND segments.date = '{yesterday}'
|
||||
"""
|
||||
result = {}
|
||||
try:
|
||||
response = ga_service.search(customer_id=self.customer_id, query=query)
|
||||
for row in response:
|
||||
cid = str(row.campaign.id)
|
||||
result[cid] = {
|
||||
"conversions": row.metrics.conversions,
|
||||
"cost": row.metrics.cost_micros / 1_000_000,
|
||||
}
|
||||
except GoogleAdsException as e:
|
||||
print(f" ❌ Error obteniendo métricas de hoy: {e}")
|
||||
return result
|
||||
|
||||
def get_campaign_metrics(self, campaign_id: str) -> dict:
|
||||
"""Métricas del mes en curso para una campaña concreta (acumulado mensual)."""
|
||||
ga_service = self.client.get_service("GoogleAdsService")
|
||||
|
||||
28
run.py
28
run.py
@ -2,6 +2,7 @@ import sys
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", line_buffering=True)
|
||||
|
||||
from airtable_client import AirtableClient
|
||||
@ -97,6 +98,7 @@ def run():
|
||||
print("→ Sincronizando campañas desde Google Ads...")
|
||||
google_campaigns = gads.get_all_campaigns()
|
||||
monthly_metrics = gads.get_monthly_metrics_all()
|
||||
today_metrics = gads.get_yesterday_metrics_all()
|
||||
print(" Calculando PPL y CapTotalMes...")
|
||||
ppl_lookup, cap_lookup = at.build_campaign_lookups()
|
||||
sync_result = at.sync_campaigns_from_google_ads(google_campaigns, monthly_metrics, ppl_lookup, cap_lookup)
|
||||
@ -194,6 +196,7 @@ def run():
|
||||
"metrics": metrics,
|
||||
"analysis": analysis,
|
||||
"decision": decision,
|
||||
"today_metrics": today_metrics.get(cid, {}),
|
||||
})
|
||||
|
||||
# Actualizar status en GACampaignMes y Google Ads Campaigns
|
||||
@ -212,6 +215,9 @@ def run():
|
||||
# === SEGUNDA PASADA: imprimir en orden + aplicar decisiones ===
|
||||
resumen = []
|
||||
advice_updates = [] # (gcm_record_id, consejo, criticidad) para batch update final
|
||||
metricas_updates = [] # {airtable_id, metricas_json} para MetricasDiarias
|
||||
from datetime import timedelta
|
||||
dia_hoy = (datetime.now() - timedelta(days=1)).strftime("%d")
|
||||
last_priority = -1
|
||||
|
||||
for item in collected:
|
||||
@ -220,6 +226,7 @@ def run():
|
||||
metrics = item["metrics"]
|
||||
analysis = item["analysis"]
|
||||
decision = item["decision"]
|
||||
today_m = item["today_metrics"]
|
||||
cid = campaign["google_campaign_id"]
|
||||
|
||||
p = _priority(item)
|
||||
@ -283,6 +290,21 @@ def run():
|
||||
elif course_num in courses_with_leadform:
|
||||
log_text += " | ⚠️ LEADFORM COMPANION: existe una campaña _leadform activa para este curso — parte de las conversiones de Google pueden provenir de leads capturados directamente en Google"
|
||||
|
||||
# Métricas diarias: coste hoy, ingreso (conversiones × PPL) y margen
|
||||
coste_hoy = round(today_m.get("cost", 0), 2)
|
||||
conv_hoy = today_m.get("conversions", 0)
|
||||
ingreso_hoy = round(conv_hoy * campaign["ppl"], 2)
|
||||
margen_hoy = round(ingreso_hoy - coste_hoy, 2)
|
||||
try:
|
||||
md = json.loads(campaign["metricas_diarias"])
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
md = {}
|
||||
md[dia_hoy] = {"coste": coste_hoy, "ingreso": ingreso_hoy, "margen": margen_hoy}
|
||||
metricas_updates.append({
|
||||
"airtable_id": campaign["airtable_id"],
|
||||
"metricas_json": json.dumps(md, ensure_ascii=False),
|
||||
})
|
||||
|
||||
advice_updates.append((
|
||||
campaign["airtable_id"],
|
||||
decision.get("consejo", ""),
|
||||
@ -310,6 +332,12 @@ def run():
|
||||
at.batch_update_gacampaignmes_advice(advice_updates)
|
||||
print(" ✓ Consejos y criticidad guardados.")
|
||||
|
||||
# Guardar métricas diarias en MetricasDiarias
|
||||
if metricas_updates:
|
||||
print(f"→ Actualizando MetricasDiarias ({len(metricas_updates)} registros)...")
|
||||
at.batch_update_metricas_diarias(metricas_updates)
|
||||
print(" ✓ MetricasDiarias actualizado.")
|
||||
|
||||
# Snapshot diario: ConvLeadsLakeMesFinal + ConvLeadsLakeMesGrupo
|
||||
# PMX con companion Search → Grupo = conversiones Google (ya calculado en leads_grupo)
|
||||
final_leads_data = [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user