leads-optimizer/backfill_metricas_30junio.py
José Manuel Gómez a7f8ddad08
Some checks failed
Weekly Strategic Report / run (push) Has been cancelled
Add backfill scripts for MetricasDiarias gaps caused by the month-change bug
One-off scripts to reconstruct missing daily coste/ingreso/margen/leads
from Google Ads and Leads Lake for days lost to the overwrite bug fixed
in run.py. backfill_metricas_mes.py generalizes the approach (mes/año
args) and was used to repair both mayo and junio 2026; the two junio-
specific scripts document the narrower fixes applied first.
2026-07-03 00:43:41 +02:00

128 lines
4.6 KiB
Python

"""
Reconstruye el día 30/06/2026 en MetricasDiarias para las GACampaignMes de
junio a las que les falta esa clave.
Motivo: en el cambio de mes (1 julio), run.py leía MetricasDiarias del
registro de julio (recién creado, casi vacío) en vez del de junio, así que
al redirigir la escritura hacia el registro de junio correcto se
sobrescribía con un dict casi vacío (perdiendo el día 30 real, que en
algunos casos quedó mal grabado en el registro de julio). Aquí se recalcula
el día 30 desde Google Ads (coste, conversiones) y Leads Lake, y se añade
SOLO a los registros de junio que no tienen ya esa clave — no se toca nada
más de su histórico.
Uso:
python backfill_metricas_30junio.py # dry run — solo muestra
python backfill_metricas_30junio.py --apply # escribe en Airtable
"""
import json
import re
import sys
from airtable_client import AirtableClient
from google_ads_client import GoogleAdsClient
DATE_STR = "2026-06-30"
DAY_KEY = "30"
MES, ANIO = 6, 2026
def _course_num(name: str) -> str | None:
m = re.search(r'fco_(?:search|pmx)_(\d+)', name, re.IGNORECASE)
return m.group(1) if m else None
def run(apply: bool):
at = AirtableClient()
gads = GoogleAdsClient()
formula = f"AND({{Mes}}='{MES}',{{Año}}='{ANIO}')"
records = at.gacampaignmes.all(
formula=formula,
fields=["CampaignID", "PPL", "MetricasDiarias", "Campaign Name (from CampaignID)"],
)
campaigns_records = at.campaigns.all(fields=["CampaignID"])
at_id_to_gid = {r["id"]: str(r["fields"].get("CampaignID", "")).strip() for r in campaigns_records}
campaigns = []
for r in records:
f = r["fields"]
at_cids = f.get("CampaignID", [])
gid = at_id_to_gid.get(at_cids[0], "") if at_cids else ""
if not gid:
continue
name_list = f.get("Campaign Name (from CampaignID)", [])
campaigns.append({
"airtable_id": r["id"],
"google_campaign_id": gid,
"curso": name_list[0] if name_list else "Sin nombre",
"ppl": float(f.get("PPL") or 0),
"metricas_diarias": f.get("MetricasDiarias") or "{}",
})
print(f"{len(campaigns)} campañas de {MES}/{ANIO} en GACampaignMes")
# Mapping cursoid → PMX campaign_id, igual que run.py, para atribuir leadforms
cursoid_to_campaign: dict[str, str] = {}
for c in campaigns:
num = _course_num(c["curso"])
if num and "pmx" in c["curso"].lower() and "_leadform" not in c["curso"].lower():
cursoid_to_campaign[num] = c["google_campaign_id"]
# Detectar registros a los que YA les falta el día 30
targets = []
for c in campaigns:
try:
md = json.loads(c["metricas_diarias"])
except (json.JSONDecodeError, TypeError):
md = {}
if DAY_KEY not in md:
c["md"] = md
targets.append(c)
print(f"{len(targets)} campañas sin el día {DAY_KEY} en su MetricasDiarias\n")
if not targets:
print("Nada que hacer.")
return
ads_metrics = gads.get_metrics_for_date(DATE_STR)
leads_lake_counts = at.get_leads_by_campaign_on_date(DATE_STR, cursoid_to_campaign)
metricas_updates = []
print(f"{'Campaña':45} {'Coste':>10} {'Conv':>6} {'Ingreso':>10} {'Margen':>10} {'LeadsLake':>10}")
for c in targets:
cid = c["google_campaign_id"]
today_m = ads_metrics.get(cid, {})
coste_hoy = round(today_m.get("cost", 0), 2)
conv_hoy = today_m.get("conversions", 0)
ingreso_hoy = round(conv_hoy * c["ppl"], 2)
margen_hoy = round(ingreso_hoy - coste_hoy, 2)
leads_lake_hoy = leads_lake_counts.get(cid, 0)
print(f"{c['curso'][:45]:45} {coste_hoy:>9.2f}{conv_hoy:>6.0f} "
f"{ingreso_hoy:>9.2f}{margen_hoy:>9.2f}{leads_lake_hoy:>10}")
md = dict(c["md"])
md[DAY_KEY] = {
"coste": coste_hoy,
"ingreso": ingreso_hoy,
"margen": margen_hoy,
"leads": int(conv_hoy),
"leads_lake": leads_lake_hoy,
}
metricas_updates.append({
"airtable_id": c["airtable_id"],
"metricas_json": json.dumps(md, ensure_ascii=False),
})
print()
if apply:
at.batch_update_metricas_diarias(metricas_updates)
print(f"{len(metricas_updates)} registros actualizados en Airtable.")
else:
print(f"DRY RUN — {len(metricas_updates)} registros se actualizarían. "
f"Ejecuta con --apply para escribir en Airtable.")
if __name__ == "__main__":
run(apply="--apply" in sys.argv)