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"AND("
|
||||||
f"OR("
|
f"OR("
|
||||||
f"{{GACampaignID}}='{campaign_id}',"
|
f"{{GACampaignID}}='{campaign_id}',"
|
||||||
f"FIND('{campaign_id}',{{GoogleCampaignID}}),"
|
f"FIND(',{campaign_id},',',' & {{GoogleCampaignID}} & ','),"
|
||||||
f"FIND('gad_campaignid={campaign_id}',{{attr_referer}})"
|
f"FIND('gad_campaignid={campaign_id}',{{attr_referer}})"
|
||||||
f"),"
|
f"),"
|
||||||
f"{{attr_utm_medium}}='paid_search',"
|
|
||||||
f"{{creado}}>='{mes_inicio}'"
|
f"{{creado}}>='{mes_inicio}'"
|
||||||
f")"
|
f")"
|
||||||
)
|
)
|
||||||
@ -372,16 +371,16 @@ class AirtableClient:
|
|||||||
for i in range(0, len(batch), 10):
|
for i in range(0, len(batch), 10):
|
||||||
self.gacampaignmes.batch_update(batch[i:i+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.
|
Actualiza en lote los campos 'Consejo', 'Criticidad' y 'Log' de GACampaignMes.
|
||||||
updates: lista de (gcm_record_id, consejo_text, criticidad)
|
updates: lista de (gcm_record_id, consejo_text, criticidad, log_text)
|
||||||
criticidad: 'Crítico' | 'Peligro' | 'Mantener'
|
criticidad: 'Crítico' | 'Peligro' | 'Mantener'
|
||||||
"""
|
"""
|
||||||
for i in range(0, len(updates), 10):
|
for i in range(0, len(updates), 10):
|
||||||
batch = [
|
batch = [
|
||||||
{"id": rid, "fields": {"Consejo": consejo, "Criticidad": criticidad}}
|
{"id": rid, "fields": {"Consejo": consejo, "Criticidad": criticidad, "Log": log}}
|
||||||
for rid, consejo, criticidad in updates[i:i+10]
|
for rid, consejo, criticidad, log in updates[i:i+10]
|
||||||
]
|
]
|
||||||
if batch:
|
if batch:
|
||||||
self.gacampaignmes.batch_update(batch, typecast=True)
|
self.gacampaignmes.batch_update(batch, typecast=True)
|
||||||
|
|||||||
@ -26,7 +26,7 @@ def analyze(campaign_config: dict, leads_entregados: int, ads_metrics: dict) ->
|
|||||||
# Urgencia
|
# Urgencia
|
||||||
if ratio_leads >= 1.0:
|
if ratio_leads >= 1.0:
|
||||||
urgencia = "PAUSAR"
|
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"
|
urgencia = "SPRINT"
|
||||||
elif ritmo < -0.15:
|
elif ritmo < -0.15:
|
||||||
urgencia = "ACELERAR"
|
urgencia = "ACELERAR"
|
||||||
|
|||||||
@ -70,7 +70,7 @@ class GoogleAdsClient:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def get_campaign_metrics(self, campaign_id: str) -> dict:
|
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")
|
ga_service = self.client.get_service("GoogleAdsService")
|
||||||
query = f"""
|
query = f"""
|
||||||
SELECT
|
SELECT
|
||||||
@ -82,27 +82,42 @@ class GoogleAdsClient:
|
|||||||
metrics.cost_micros,
|
metrics.cost_micros,
|
||||||
metrics.conversions,
|
metrics.conversions,
|
||||||
metrics.clicks,
|
metrics.clicks,
|
||||||
metrics.impressions,
|
metrics.impressions
|
||||||
metrics.ctr
|
|
||||||
FROM campaign
|
FROM campaign
|
||||||
WHERE campaign.id = {campaign_id}
|
WHERE campaign.id = {campaign_id}
|
||||||
AND segments.date DURING THIS_MONTH
|
AND segments.date DURING THIS_MONTH
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
response = ga_service.search(customer_id=self.customer_id, query=query)
|
response = ga_service.search(customer_id=self.customer_id, query=query)
|
||||||
|
cost = conversions = clicks = impressions = 0
|
||||||
|
meta = {}
|
||||||
for row in response:
|
for row in response:
|
||||||
m = row.metrics
|
m = row.metrics
|
||||||
return {
|
cost += m.cost_micros / 1_000_000
|
||||||
"campaign_id": campaign_id,
|
conversions += m.conversions
|
||||||
|
clicks += m.clicks
|
||||||
|
impressions += m.impressions
|
||||||
|
if not meta:
|
||||||
|
meta = {
|
||||||
"name": row.campaign.name,
|
"name": row.campaign.name,
|
||||||
"status": row.campaign.status.name,
|
"status": row.campaign.status.name,
|
||||||
"budget_daily": row.campaign_budget.amount_micros / 1_000_000,
|
"budget_daily": row.campaign_budget.amount_micros / 1_000_000,
|
||||||
"budget_resource_name": row.campaign_budget.resource_name,
|
"budget_resource_name": row.campaign_budget.resource_name,
|
||||||
"cost": m.cost_micros / 1_000_000,
|
}
|
||||||
"conversions": m.conversions,
|
if not meta:
|
||||||
"clicks": m.clicks,
|
return {}
|
||||||
"impressions": m.impressions,
|
ctr = round(clicks / impressions * 100, 2) if impressions > 0 else 0.0
|
||||||
"ctr": round(m.ctr * 100, 2),
|
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:
|
except GoogleAdsException as e:
|
||||||
print(f" ❌ Error Google Ads para campaña {campaign_id}: {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)
|
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((
|
advice_updates.append((
|
||||||
campaign["airtable_id"],
|
campaign["airtable_id"],
|
||||||
decision.get("consejo", ""),
|
decision.get("consejo", ""),
|
||||||
CRITICIDAD_MAP[p],
|
CRITICIDAD_MAP[p],
|
||||||
|
log_text,
|
||||||
))
|
))
|
||||||
|
|
||||||
resumen.append({
|
resumen.append({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user