Add daily leads count per campaign to MetricasDiarias
- Fetch leads from Airtable per campaign on yesterday's date before second pass - Include 'leads' field in MetricasDiarias JSON alongside coste/ingreso/margen - Add get_leads_by_campaign_on_date() bulk fetch in AirtableClient - Add backfill_leads_mayo.py one-off script (already executed for days 01-03) - Fix UnboundLocalError: remove duplicate local timedelta import inside run() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
26eeff794c
commit
f1dec1c887
@ -1,6 +1,7 @@
|
|||||||
from pyairtable import Api
|
from pyairtable import Api
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import config
|
import config
|
||||||
|
|
||||||
|
|
||||||
@ -363,6 +364,32 @@ class AirtableClient:
|
|||||||
ids = [r["id"] for r in records]
|
ids = [r["id"] for r in records]
|
||||||
return len(ids), ids
|
return len(ids), ids
|
||||||
|
|
||||||
|
def get_leads_by_campaign_on_date(self, date_str: str) -> dict:
|
||||||
|
"""
|
||||||
|
Devuelve {google_campaign_id: count} para todos los leads de un día concreto.
|
||||||
|
Una sola llamada bulk para todos los campañas — más eficiente que una por campaña.
|
||||||
|
date_str: 'YYYY-MM-DD'
|
||||||
|
"""
|
||||||
|
next_day = (datetime.strptime(date_str, "%Y-%m-%d") + timedelta(days=1)).strftime("%Y-%m-%d")
|
||||||
|
formula = f"AND({{creado}}>='{date_str}',{{creado}}<'{next_day}')"
|
||||||
|
records = self.leads.all(
|
||||||
|
formula=formula,
|
||||||
|
fields=["GACampaignID", "GoogleCampaignID", "attr_referer"],
|
||||||
|
)
|
||||||
|
counts: dict[str, int] = {}
|
||||||
|
for r in records:
|
||||||
|
f = r["fields"]
|
||||||
|
cid = str(f.get("GACampaignID") or "").strip()
|
||||||
|
if not cid:
|
||||||
|
cid = str(f.get("GoogleCampaignID") or "").strip()
|
||||||
|
if not cid:
|
||||||
|
m = re.search(r"gad_campaignid=(\d+)", f.get("attr_referer", ""))
|
||||||
|
if m:
|
||||||
|
cid = m.group(1)
|
||||||
|
if cid:
|
||||||
|
counts[cid] = counts.get(cid, 0) + 1
|
||||||
|
return counts
|
||||||
|
|
||||||
def update_gacampaignmes_leads_lake(self, gcm_record_id: str, lead_ids: list[str]) -> None:
|
def update_gacampaignmes_leads_lake(self, gcm_record_id: str, lead_ids: list[str]) -> None:
|
||||||
"""Actualiza el campo 'Leads Lake' de un registro GACampaignMes."""
|
"""Actualiza el campo 'Leads Lake' de un registro GACampaignMes."""
|
||||||
self.gacampaignmes.update(gcm_record_id, {"Leads Lake": lead_ids})
|
self.gacampaignmes.update(gcm_record_id, {"Leads Lake": lead_ids})
|
||||||
|
|||||||
79
backfill_leads_mayo.py
Normal file
79
backfill_leads_mayo.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
"""
|
||||||
|
Script one-off: rellena el campo 'leads' en MetricasDiarias para cada día
|
||||||
|
de mayo ya registrado en GACampaignMes.
|
||||||
|
|
||||||
|
Ejecutar una sola vez:
|
||||||
|
python backfill_leads_mayo.py
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from airtable_client import AirtableClient
|
||||||
|
|
||||||
|
at = AirtableClient()
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
print("Cargando registros GACampaignMes de mayo...")
|
||||||
|
records = at.gacampaignmes.all(
|
||||||
|
formula="AND({Mes}='5',{Año}='2026')",
|
||||||
|
fields=["CampaignID", "MetricasDiarias", "Campaign Name (from CampaignID)"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mapping google_campaign_id → (gcm_record_id, metricas_dict)
|
||||||
|
gid_to_gcm: dict[str, tuple[str, dict]] = {}
|
||||||
|
campaigns_records = at.campaigns.all(fields=["CampaignID"])
|
||||||
|
at_id_to_gid = {r["id"]: str(r["fields"].get("CampaignID", "")).strip() for r in campaigns_records}
|
||||||
|
|
||||||
|
for r in records:
|
||||||
|
at_cids = r["fields"].get("CampaignID", [])
|
||||||
|
if not at_cids:
|
||||||
|
continue
|
||||||
|
gid = at_id_to_gid.get(at_cids[0], "")
|
||||||
|
if not gid:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
md = json.loads(r["fields"].get("MetricasDiarias") or "{}")
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
md = {}
|
||||||
|
gid_to_gcm[gid] = (r["id"], md)
|
||||||
|
|
||||||
|
# Días de mayo que ya tienen métricas (sin 'leads')
|
||||||
|
days_needed: set[str] = set()
|
||||||
|
for _, (_, md) in gid_to_gcm.items():
|
||||||
|
for day_str, vals in md.items():
|
||||||
|
if "leads" not in vals:
|
||||||
|
days_needed.add(day_str)
|
||||||
|
|
||||||
|
if not days_needed:
|
||||||
|
print("Todos los días ya tienen 'leads'. Nada que hacer.")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
print(f"Días a rellenar: {sorted(days_needed)}")
|
||||||
|
|
||||||
|
# Para cada día, obtener leads bulk
|
||||||
|
for day_str in sorted(days_needed):
|
||||||
|
try:
|
||||||
|
day_int = int(day_str)
|
||||||
|
date = datetime(now.year, 5, day_int)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
date_iso = date.strftime("%Y-%m-%d")
|
||||||
|
print(f" > {date_iso} ...", end=" ", flush=True)
|
||||||
|
leads_by_cid = at.get_leads_by_campaign_on_date(date_iso)
|
||||||
|
updated = 0
|
||||||
|
for gid, (gcm_id, md) in gid_to_gcm.items():
|
||||||
|
if day_str in md and "leads" not in md[day_str]:
|
||||||
|
md[day_str]["leads"] = leads_by_cid.get(gid, 0)
|
||||||
|
updated += 1
|
||||||
|
print(f"{updated} campañas actualizadas")
|
||||||
|
|
||||||
|
# Guardar en Airtable en lotes
|
||||||
|
print("\nGuardando en Airtable...")
|
||||||
|
batch = [
|
||||||
|
{"id": gcm_id, "fields": {"MetricasDiarias": json.dumps(md, ensure_ascii=False)}}
|
||||||
|
for _, (gcm_id, md) in gid_to_gcm.items()
|
||||||
|
if any("leads" in v for v in md.values())
|
||||||
|
]
|
||||||
|
for i in range(0, len(batch), 10):
|
||||||
|
at.gacampaignmes.batch_update(batch[i:i+10])
|
||||||
|
|
||||||
|
print(f"✓ Backfill completado: {len(batch)} registros actualizados.")
|
||||||
8
run.py
8
run.py
@ -12,7 +12,7 @@ from agent import decide
|
|||||||
from optimizer import apply_decision
|
from optimizer import apply_decision
|
||||||
from slack_reporter import build_and_send
|
from slack_reporter import build_and_send
|
||||||
import config
|
import config
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
|
||||||
class Tee:
|
class Tee:
|
||||||
@ -100,6 +100,8 @@ def run():
|
|||||||
google_campaigns = gads.get_all_campaigns()
|
google_campaigns = gads.get_all_campaigns()
|
||||||
monthly_metrics = gads.get_monthly_metrics_all()
|
monthly_metrics = gads.get_monthly_metrics_all()
|
||||||
today_metrics = gads.get_yesterday_metrics_all()
|
today_metrics = gads.get_yesterday_metrics_all()
|
||||||
|
_ayer_date = (datetime.now() - timedelta(days=1))
|
||||||
|
leads_yesterday = at.get_leads_by_campaign_on_date(_ayer_date.strftime("%Y-%m-%d"))
|
||||||
print(" Calculando PPL y CapTotalMes...")
|
print(" Calculando PPL y CapTotalMes...")
|
||||||
ppl_lookup, cap_lookup = at.build_campaign_lookups()
|
ppl_lookup, cap_lookup = at.build_campaign_lookups()
|
||||||
sync_result = at.sync_campaigns_from_google_ads(google_campaigns, monthly_metrics, ppl_lookup, cap_lookup)
|
sync_result = at.sync_campaigns_from_google_ads(google_campaigns, monthly_metrics, ppl_lookup, cap_lookup)
|
||||||
@ -217,7 +219,6 @@ def run():
|
|||||||
resumen = []
|
resumen = []
|
||||||
advice_updates = [] # (gcm_record_id, consejo, criticidad) para batch update final
|
advice_updates = [] # (gcm_record_id, consejo, criticidad) para batch update final
|
||||||
metricas_updates = [] # {airtable_id, metricas_json} para MetricasDiarias
|
metricas_updates = [] # {airtable_id, metricas_json} para MetricasDiarias
|
||||||
from datetime import timedelta
|
|
||||||
ayer = datetime.now() - timedelta(days=1)
|
ayer = datetime.now() - timedelta(days=1)
|
||||||
dia_hoy = ayer.strftime("%d")
|
dia_hoy = ayer.strftime("%d")
|
||||||
cambio_mes = ayer.month != datetime.now().month
|
cambio_mes = ayer.month != datetime.now().month
|
||||||
@ -302,7 +303,8 @@ def run():
|
|||||||
md = json.loads(campaign["metricas_diarias"])
|
md = json.loads(campaign["metricas_diarias"])
|
||||||
except (json.JSONDecodeError, TypeError):
|
except (json.JSONDecodeError, TypeError):
|
||||||
md = {}
|
md = {}
|
||||||
md[dia_hoy] = {"coste": coste_hoy, "ingreso": ingreso_hoy, "margen": margen_hoy}
|
leads_hoy = leads_yesterday.get(cid, 0)
|
||||||
|
md[dia_hoy] = {"coste": coste_hoy, "ingreso": ingreso_hoy, "margen": margen_hoy, "leads": leads_hoy}
|
||||||
campaign["metricas_diarias"] = json.dumps(md, ensure_ascii=False)
|
campaign["metricas_diarias"] = json.dumps(md, ensure_ascii=False)
|
||||||
metricas_updates.append({
|
metricas_updates.append({
|
||||||
"airtable_id": campaign["airtable_id"],
|
"airtable_id": campaign["airtable_id"],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user