Fix month-change MetricasDiarias overwrite bug, add per-date Ads metrics helpers

On month change, run.py redirected the write to the previous month's
GACampaignMes record but built the JSON from the new month's near-empty
record, overwriting the previous month's full daily history with just
the redirected day. Now it merges with the previous month's real
MetricasDiarias (via get_metricas_diarias_prev_month) before writing.

Also add get_metrics_for_date() and get_daily_metrics_for_month() to
GoogleAdsClient for reconstructing historical daily cost/conversions,
needed by the backfill scripts.
This commit is contained in:
Jose Manuel 2026-07-03 00:43:33 +02:00
parent ea201ef565
commit 31193c57ee
2 changed files with 77 additions and 1 deletions

View File

@ -100,6 +100,70 @@ class GoogleAdsClient:
print(f" ❌ Error obteniendo métricas de hoy: {e}") print(f" ❌ Error obteniendo métricas de hoy: {e}")
return result return result
def get_metrics_for_date(self, date_str: str) -> dict:
"""
Devuelve métricas de una fecha concreta ('YYYY-MM-DD') para TODAS las
campañas en una sola query. Retorna dict {campaign_id: {conversions, cost}}.
"""
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 = '{date_str}'
"""
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 del {date_str}: {e}")
return result
def get_daily_metrics_for_month(self, year: int, month: int) -> dict:
"""
Devuelve coste y conversiones diarias de todas las campañas para un mes
completo. Retorna dict {date_str ('YYYY-MM-DD'): {campaign_id: {cost, conversions}}}.
"""
start = f"{year}-{month:02d}-01"
if month == 12:
end = f"{year + 1}-01-01"
else:
end = f"{year}-{month + 1:02d}-01"
ga_service = self.client.get_service("GoogleAdsService")
query = f"""
SELECT
campaign.id,
segments.date,
metrics.conversions,
metrics.cost_micros
FROM campaign
WHERE campaign.status != 'REMOVED'
AND segments.date >= '{start}'
AND segments.date < '{end}'
"""
result: dict = {}
try:
response = ga_service.search(customer_id=self.customer_id, query=query)
for row in response:
date_str = row.segments.date
cid = str(row.campaign.id)
result.setdefault(date_str, {})[cid] = {
"conversions": row.metrics.conversions,
"cost": row.metrics.cost_micros / 1_000_000,
}
except GoogleAdsException as e:
print(f" ❌ Error obteniendo métricas diarias del mes: {e}")
return result
def get_daily_conversions_for_month(self, year: int, month: int) -> dict: def get_daily_conversions_for_month(self, year: int, month: int) -> dict:
""" """
Devuelve conversiones diarias de todas las campañas para un mes completo. Devuelve conversiones diarias de todas las campañas para un mes completo.

14
run.py
View File

@ -398,8 +398,13 @@ def run():
if metricas_updates: if metricas_updates:
print(f"→ Actualizando MetricasDiarias ({len(metricas_updates)} registros)...") print(f"→ Actualizando MetricasDiarias ({len(metricas_updates)} registros)...")
if cambio_mes: if cambio_mes:
# Ayer pertenece al mes anterior: redirigir escritura al GACampaignMes correcto # Ayer pertenece al mes anterior: redirigir escritura al GACampaignMes correcto.
# El "campaign" en memoria es el del mes nuevo (recién creado, MetricasDiarias
# casi vacío) — hay que hacer merge con el MetricasDiarias REAL del mes anterior
# antes de escribir, o se pierde el resto de días ya acumulados (bug que borró
# el histórico de junio salvo el día 30 al hacer un overwrite directo).
prev_map = at.get_gcm_id_map_for_month(ayer.month, ayer.year) prev_map = at.get_gcm_id_map_for_month(ayer.month, ayer.year)
prev_metricas = at.get_metricas_diarias_prev_month()
redirected = [] redirected = []
omitidos = [] omitidos = []
for u in metricas_updates: for u in metricas_updates:
@ -409,7 +414,14 @@ def run():
None, None,
) )
if gid and gid in prev_map: if gid and gid in prev_map:
try:
existing_md = json.loads(prev_metricas.get(gid, {}).get("metricas") or "{}")
except (json.JSONDecodeError, TypeError):
existing_md = {}
nuevo_dia_md = json.loads(u["metricas_json"])
existing_md.update(nuevo_dia_md)
u["airtable_id"] = prev_map[gid] u["airtable_id"] = prev_map[gid]
u["metricas_json"] = json.dumps(existing_md, ensure_ascii=False)
redirected.append(u) redirected.append(u)
else: else:
omitidos.append(gid) omitidos.append(gid)