Multiple fixes: lead matching, metrics aggregation, Log field, SPRINT with capping=0
- Fix GoogleCampaignID matching with comma delimiters (exact word boundary) - Remove attr_utm_medium filter (campaign ID filter is specific enough) - Fix get_campaign_metrics: accumulate all daily rows for monthly totals - Fix SPRINT urgency: only trigger when capping > 0 - Add Log field to GACampaignMes with Airtable vs Google Ads discrepancy info - batch_update_gacampaignmes_advice now also updates Log field Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5639bd6e76
commit
a1aef8f58e
@ -344,10 +344,9 @@ class AirtableClient:
|
||||
f"AND("
|
||||
f"OR("
|
||||
f"{{GACampaignID}}='{campaign_id}',"
|
||||
f"FIND('{campaign_id}',{{GoogleCampaignID}}),"
|
||||
f"FIND(',{campaign_id},',',' & {{GoogleCampaignID}} & ','),"
|
||||
f"FIND('gad_campaignid={campaign_id}',{{attr_referer}})"
|
||||
f"),"
|
||||
f"{{attr_utm_medium}}='paid_search',"
|
||||
f"{{creado}}>='{mes_inicio}'"
|
||||
f")"
|
||||
)
|
||||
@ -372,16 +371,16 @@ class AirtableClient:
|
||||
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[str, str, str]]) -> None:
|
||||
def batch_update_gacampaignmes_advice(self, updates: list[tuple]) -> None:
|
||||
"""
|
||||
Actualiza en lote los campos 'Consejo' y 'Criticidad' de GACampaignMes.
|
||||
updates: lista de (gcm_record_id, consejo_text, criticidad)
|
||||
Actualiza en lote los campos 'Consejo', 'Criticidad' y 'Log' de GACampaignMes.
|
||||
updates: lista de (gcm_record_id, consejo_text, criticidad, log_text)
|
||||
criticidad: 'Crítico' | 'Peligro' | 'Mantener'
|
||||
"""
|
||||
for i in range(0, len(updates), 10):
|
||||
batch = [
|
||||
{"id": rid, "fields": {"Consejo": consejo, "Criticidad": criticidad}}
|
||||
for rid, consejo, criticidad in updates[i:i+10]
|
||||
{"id": rid, "fields": {"Consejo": consejo, "Criticidad": criticidad, "Log": log}}
|
||||
for rid, consejo, criticidad, log in updates[i:i+10]
|
||||
]
|
||||
if batch:
|
||||
self.gacampaignmes.batch_update(batch, typecast=True)
|
||||
|
||||
@ -26,7 +26,7 @@ def analyze(campaign_config: dict, leads_entregados: int, ads_metrics: dict) ->
|
||||
# Urgencia
|
||||
if ratio_leads >= 1.0:
|
||||
urgencia = "PAUSAR"
|
||||
elif ratio_leads < ratio_mes - 0.15 and dias_restantes <= 5:
|
||||
elif capping > 0 and ratio_leads < ratio_mes - 0.15 and dias_restantes <= 5:
|
||||
urgencia = "SPRINT"
|
||||
elif ritmo < -0.15:
|
||||
urgencia = "ACELERAR"
|
||||
|
||||
@ -70,7 +70,7 @@ class GoogleAdsClient:
|
||||
return result
|
||||
|
||||
def get_campaign_metrics(self, campaign_id: str) -> dict:
|
||||
"""Métricas del mes en curso para una campaña concreta."""
|
||||
"""Métricas del mes en curso para una campaña concreta (acumulado mensual)."""
|
||||
ga_service = self.client.get_service("GoogleAdsService")
|
||||
query = f"""
|
||||
SELECT
|
||||
@ -82,27 +82,42 @@ class GoogleAdsClient:
|
||||
metrics.cost_micros,
|
||||
metrics.conversions,
|
||||
metrics.clicks,
|
||||
metrics.impressions,
|
||||
metrics.ctr
|
||||
metrics.impressions
|
||||
FROM campaign
|
||||
WHERE campaign.id = {campaign_id}
|
||||
AND segments.date DURING THIS_MONTH
|
||||
"""
|
||||
try:
|
||||
response = ga_service.search(customer_id=self.customer_id, query=query)
|
||||
cost = conversions = clicks = impressions = 0
|
||||
meta = {}
|
||||
for row in response:
|
||||
m = row.metrics
|
||||
return {
|
||||
"campaign_id": campaign_id,
|
||||
cost += m.cost_micros / 1_000_000
|
||||
conversions += m.conversions
|
||||
clicks += m.clicks
|
||||
impressions += m.impressions
|
||||
if not meta:
|
||||
meta = {
|
||||
"name": row.campaign.name,
|
||||
"status": row.campaign.status.name,
|
||||
"budget_daily": row.campaign_budget.amount_micros / 1_000_000,
|
||||
"budget_resource_name": row.campaign_budget.resource_name,
|
||||
"cost": m.cost_micros / 1_000_000,
|
||||
"conversions": m.conversions,
|
||||
"clicks": m.clicks,
|
||||
"impressions": m.impressions,
|
||||
"ctr": round(m.ctr * 100, 2),
|
||||
}
|
||||
if not meta:
|
||||
return {}
|
||||
ctr = round(clicks / impressions * 100, 2) if impressions > 0 else 0.0
|
||||
return {
|
||||
"campaign_id": campaign_id,
|
||||
"name": meta["name"],
|
||||
"status": meta["status"],
|
||||
"budget_daily": meta["budget_daily"],
|
||||
"budget_resource_name": meta["budget_resource_name"],
|
||||
"cost": round(cost, 2),
|
||||
"conversions": conversions,
|
||||
"clicks": clicks,
|
||||
"impressions": impressions,
|
||||
"ctr": ctr,
|
||||
}
|
||||
except GoogleAdsException as e:
|
||||
print(f" ❌ Error Google Ads para campaña {campaign_id}: {e}")
|
||||
|
||||
11
run.py
11
run.py
@ -225,10 +225,21 @@ def run():
|
||||
|
||||
apply_decision(campaign, decision, metrics, gads)
|
||||
|
||||
conv_google = int(analysis["conversiones_google"])
|
||||
discrepancia_fresh = abs(conv_google - leads)
|
||||
if discrepancia_fresh > 10:
|
||||
log_text = (
|
||||
f"⚠️ DISCREPANCIA: {leads} leads Airtable vs {conv_google} conv Google Ads "
|
||||
f"(Δ {discrepancia_fresh})"
|
||||
)
|
||||
else:
|
||||
log_text = f"OK: {leads} leads Airtable | {conv_google} conv Google Ads"
|
||||
|
||||
advice_updates.append((
|
||||
campaign["airtable_id"],
|
||||
decision.get("consejo", ""),
|
||||
CRITICIDAD_MAP[p],
|
||||
log_text,
|
||||
))
|
||||
|
||||
resumen.append({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user