From 858755a6b8f679f79bc06e95a4544fed90a9c883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20G=C3=B3mez?= Date: Sat, 13 Jun 2026 09:41:52 +0200 Subject: [PATCH] Disable ambiguous attribution paths for cursoids with multiple PMX campaigns When multiple PMX campaigns share the same cursoid (e.g. PMX + Reset), paths 4 (cursoid+utm_source) and 5 (cursoid+UserAgent) are disabled for all of them to prevent double-counting Airtable leads. Only direct paths (GACampaignID, GoogleCampaignID, attr_referer) are used in those cases. Co-Authored-By: Claude Sonnet 4.6 --- airtable_client.py | 10 ++++++---- backfill_leadform_jun8_10.py | 12 +++++++++++- run.py | 12 +++++++++++- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/airtable_client.py b/airtable_client.py index e0984e9..68514ca 100644 --- a/airtable_client.py +++ b/airtable_client.py @@ -336,7 +336,7 @@ class AirtableClient: for i in range(0, len(cat_batch), 10): self.campaigns.batch_update(cat_batch[i:i+10]) - def get_leads_this_month_gads(self, campaign_id: str, campaign_name: str = "") -> tuple[int, list[str]]: + def get_leads_this_month_gads(self, campaign_id: str, campaign_name: str = "", ambiguous_cursoids: set = None) -> tuple[int, list[str]]: """ Leads del mes actual para una campaña de Google Ads. Cubre cinco vías de atribución: @@ -362,7 +362,9 @@ class AirtableClient: is_pmx_non_leadform = ( "pmx" in campaign_name.lower() and "_leadform" not in campaign_name.lower() ) - if is_pmx_non_leadform: + # Paths 4 y 5 se desactivan para PMX cuando hay múltiples PMX del mismo curso + is_ambiguous_pmx = is_pmx_non_leadform and bool(ambiguous_cursoids and course_num in ambiguous_cursoids) + if is_pmx_non_leadform and not is_ambiguous_pmx: utm_source = "pmx" elif "search" in campaign_name.lower(): utm_source = "google" @@ -373,8 +375,8 @@ class AirtableClient: f"AND({{attr_cursoid}}='{course_num}'," f"{{attr_utm_source}}='{utm_source}')" ) - # Path 5: leadform leads en Airtable, solo para PMX (no search, no _leadform) - if is_pmx_non_leadform: + # Path 5: leadform leads en Airtable, solo para PMX único (no search, no _leadform, no ambiguo) + if is_pmx_non_leadform and not is_ambiguous_pmx: leadform_cursoid_clause = ( f"AND({{attr_cursoid}}='{course_num}'," f"{{UserAgent del visitante}}='Google-Ads-Notifications')" diff --git a/backfill_leadform_jun8_10.py b/backfill_leadform_jun8_10.py index 7371a98..31c8449 100644 --- a/backfill_leadform_jun8_10.py +++ b/backfill_leadform_jun8_10.py @@ -74,12 +74,22 @@ def run(): else: print(" ✓ MetricasDiarias sin cambios.") + # Cursoids con múltiples campañas PMX activas (mismo criterio que run.py) + pmx_count_by_cursoid: dict[str, int] = {} + for c in campaigns: + num = _course_num(c["curso"]) + if num and "pmx" in c["curso"].lower() and "_leadform" not in c["curso"].lower(): + pmx_count_by_cursoid[num] = pmx_count_by_cursoid.get(num, 0) + 1 + ambiguous_pmx_cursoids = {num for num, cnt in pmx_count_by_cursoid.items() if cnt > 1} + if ambiguous_pmx_cursoids: + print(f" ⚠️ Cursoids con múltiples PMX (paths 4/5 desactivados): {ambiguous_pmx_cursoids}") + # Recalcular totales mensuales con la nueva atribución print("\n→ Recalculando totales mensuales (leads_lake acumulado del mes)...") final_leads_data = [] for campaign in campaigns: cid = campaign["google_campaign_id"] - leads, lead_ids = at.get_leads_this_month_gads(cid, campaign["curso"]) + leads, lead_ids = at.get_leads_this_month_gads(cid, campaign["curso"], ambiguous_pmx_cursoids) at.update_gacampaignmes_leads_lake(campaign["airtable_id"], lead_ids) final_leads_data.append({ "airtable_id": campaign["airtable_id"], diff --git a/run.py b/run.py index 2881c4f..002d6b8 100644 --- a/run.py +++ b/run.py @@ -155,6 +155,16 @@ def run(): if "_leadform" in c["curso"].lower() and _course_num(c["curso"]) } + # Cursoids con múltiples campañas PMX activas → paths 4/5 ambiguos, desactivar para evitar doble conteo + pmx_count_by_cursoid: dict[str, int] = {} + for c in campaigns: + num = _course_num(c["curso"]) + if num and "pmx" in c["curso"].lower() and "_leadform" not in c["curso"].lower(): + pmx_count_by_cursoid[num] = pmx_count_by_cursoid.get(num, 0) + 1 + ambiguous_pmx_cursoids = {num for num, cnt in pmx_count_by_cursoid.items() if cnt > 1} + if ambiguous_pmx_cursoids: + print(f" ⚠️ Cursoids con múltiples PMX (paths 4/5 desactivados): {ambiguous_pmx_cursoids}") + # Precomputar conversiones Google de cada campaña leadform por número de curso leadform_conv_by_course: dict[str, int] = {} for c in campaigns: @@ -183,7 +193,7 @@ def run(): for campaign in campaigns: cid = campaign["google_campaign_id"] - leads, lead_ids = at.get_leads_this_month_gads(cid, campaign["curso"]) + leads, lead_ids = at.get_leads_this_month_gads(cid, campaign["curso"], ambiguous_pmx_cursoids) at.update_gacampaignmes_leads_lake(campaign["airtable_id"], lead_ids) metrics = monthly_metrics.get(cid)