Add PMX attribution detection and ConvLeadsLakeMesGrupo field

- Detect Search+PMX companion campaigns by course number
- Add PMX attribution warning to Log field for Search campaigns with active PMX
- Add ConvLeadsLakeMesGrupo: for PMX with Search companion uses Search leads count

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jose Manuel 2026-04-25 14:46:03 +02:00
parent a1aef8f58e
commit 9edd74b9ad
2 changed files with 53 additions and 9 deletions

View File

@ -360,12 +360,18 @@ class AirtableClient:
def batch_update_gacampaignmes_final_leads(self, gcm_records: list[dict]) -> None: def batch_update_gacampaignmes_final_leads(self, gcm_records: list[dict]) -> None:
""" """
Copia ConvLeadsLakeMes ConvLeadsLakeMesFinal en GACampaignMes. Actualiza ConvLeadsLakeMesFinal y ConvLeadsLakeMesGrupo en GACampaignMes.
Se llama una vez por ejecución diaria. gcm_records: lista de dicts con:
gcm_records: lista de dicts con 'airtable_id' y 'conv_leads_lake_mes' - 'airtable_id'
- 'conv_leads_lake_mes' valor para ConvLeadsLakeMesFinal
- 'conv_leads_lake_mes_grupo' valor para ConvLeadsLakeMesGrupo
(igual al anterior salvo en PMX con Search companion: usa leads del Search)
""" """
batch = [ batch = [
{"id": r["airtable_id"], "fields": {"ConvLeadsLakeMesFinal": r["conv_leads_lake_mes"]}} {"id": r["airtable_id"], "fields": {
"ConvLeadsLakeMesFinal": r["conv_leads_lake_mes"],
"ConvLeadsLakeMesGrupo": r["conv_leads_lake_mes_grupo"],
}}
for r in gcm_records for r in gcm_records
] ]
for i in range(0, len(batch), 10): for i in range(0, len(batch), 10):

48
run.py
View File

@ -1,6 +1,7 @@
import sys import sys
import io import io
import os import os
import re
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", line_buffering=True) sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", line_buffering=True)
from airtable_client import AirtableClient from airtable_client import AirtableClient
@ -173,6 +174,23 @@ def run():
# Ordenar: 0=críticas → 1=no críticas → 2=mantener # Ordenar: 0=críticas → 1=no críticas → 2=mantener
collected.sort(key=_priority) collected.sort(key=_priority)
# Detectar cursos con campaña Search Y PMX simultáneas (posible reatribución PMX)
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
course_types: dict[str, set] = {}
for item in collected:
name = item["campaign"]["curso"]
num = _course_num(name)
if num:
course_types.setdefault(num, set())
if "pmx" in name.lower():
course_types[num].add("pmx")
elif "search" in name.lower():
course_types[num].add("search")
courses_with_both = {num for num, types in course_types.items() if "pmx" in types and "search" in types}
# === SEGUNDA PASADA: imprimir en orden + aplicar decisiones === # === SEGUNDA PASADA: imprimir en orden + aplicar decisiones ===
resumen = [] resumen = []
advice_updates = [] # (gcm_record_id, consejo, criticidad) para batch update final advice_updates = [] # (gcm_record_id, consejo, criticidad) para batch update final
@ -235,6 +253,11 @@ def run():
else: else:
log_text = f"OK: {leads} leads Airtable | {conv_google} conv Google Ads" log_text = f"OK: {leads} leads Airtable | {conv_google} conv Google Ads"
# Nota de reatribución PMX para campañas Search con companion PMX del mismo curso
course_num = _course_num(campaign["curso"])
if "search" in campaign["curso"].lower() and course_num in courses_with_both:
log_text += " | ⚠️ PMX ATTRIBUTION: campaña Search con companion PMX activo — parte de las conversiones de Google pueden estar reatribuidas a la campaña PMX"
advice_updates.append(( advice_updates.append((
campaign["airtable_id"], campaign["airtable_id"],
decision.get("consejo", ""), decision.get("consejo", ""),
@ -262,12 +285,27 @@ def run():
at.batch_update_gacampaignmes_advice(advice_updates) at.batch_update_gacampaignmes_advice(advice_updates)
print(" ✓ Consejos y criticidad guardados.") print(" ✓ Consejos y criticidad guardados.")
# Copiar ConvLeadsLakeMes → ConvLeadsLakeMesFinal (snapshot diario) # Snapshot diario: ConvLeadsLakeMesFinal + ConvLeadsLakeMesGrupo
final_leads_data = [ # Para PMX con Search companion del mismo curso, Grupo = leads del Search
{"airtable_id": item["campaign"]["airtable_id"], course_search_leads = {
"conv_leads_lake_mes": item["leads"]} _course_num(item["campaign"]["curso"]): item["leads"]
for item in collected for item in collected
] if "search" in item["campaign"]["curso"].lower()
and _course_num(item["campaign"]["curso"])
}
final_leads_data = []
for item in collected:
name = item["campaign"]["curso"]
num = _course_num(name)
if num and "pmx" in name.lower() and num in courses_with_both:
grupo = course_search_leads.get(num, item["leads"])
else:
grupo = item["leads"]
final_leads_data.append({
"airtable_id": item["campaign"]["airtable_id"],
"conv_leads_lake_mes": item["leads"],
"conv_leads_lake_mes_grupo": grupo,
})
if final_leads_data: if final_leads_data:
print(f"→ Actualizando ConvLeadsLakeMesFinal ({len(final_leads_data)} registros)...") print(f"→ Actualizando ConvLeadsLakeMesFinal ({len(final_leads_data)} registros)...")
at.batch_update_gacampaignmes_final_leads(final_leads_data) at.batch_update_gacampaignmes_final_leads(final_leads_data)