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 <noreply@anthropic.com>
This commit is contained in:
Jose Manuel 2026-06-13 09:41:52 +02:00
parent 4d03ddf271
commit 858755a6b8
3 changed files with 28 additions and 6 deletions

View File

@ -336,7 +336,7 @@ class AirtableClient:
for i in range(0, len(cat_batch), 10): for i in range(0, len(cat_batch), 10):
self.campaigns.batch_update(cat_batch[i:i+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. Leads del mes actual para una campaña de Google Ads.
Cubre cinco vías de atribución: Cubre cinco vías de atribución:
@ -362,7 +362,9 @@ class AirtableClient:
is_pmx_non_leadform = ( is_pmx_non_leadform = (
"pmx" in campaign_name.lower() and "_leadform" not in campaign_name.lower() "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" utm_source = "pmx"
elif "search" in campaign_name.lower(): elif "search" in campaign_name.lower():
utm_source = "google" utm_source = "google"
@ -373,8 +375,8 @@ class AirtableClient:
f"AND({{attr_cursoid}}='{course_num}'," f"AND({{attr_cursoid}}='{course_num}',"
f"{{attr_utm_source}}='{utm_source}')" f"{{attr_utm_source}}='{utm_source}')"
) )
# Path 5: leadform leads en Airtable, solo para PMX (no search, no _leadform) # Path 5: leadform leads en Airtable, solo para PMX único (no search, no _leadform, no ambiguo)
if is_pmx_non_leadform: if is_pmx_non_leadform and not is_ambiguous_pmx:
leadform_cursoid_clause = ( leadform_cursoid_clause = (
f"AND({{attr_cursoid}}='{course_num}'," f"AND({{attr_cursoid}}='{course_num}',"
f"{{UserAgent del visitante}}='Google-Ads-Notifications')" f"{{UserAgent del visitante}}='Google-Ads-Notifications')"

View File

@ -74,12 +74,22 @@ def run():
else: else:
print(" ✓ MetricasDiarias sin cambios.") 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 # Recalcular totales mensuales con la nueva atribución
print("\n→ Recalculando totales mensuales (leads_lake acumulado del mes)...") print("\n→ Recalculando totales mensuales (leads_lake acumulado del mes)...")
final_leads_data = [] final_leads_data = []
for campaign in campaigns: for campaign in campaigns:
cid = campaign["google_campaign_id"] 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) at.update_gacampaignmes_leads_lake(campaign["airtable_id"], lead_ids)
final_leads_data.append({ final_leads_data.append({
"airtable_id": campaign["airtable_id"], "airtable_id": campaign["airtable_id"],

12
run.py
View File

@ -155,6 +155,16 @@ def run():
if "_leadform" in c["curso"].lower() and _course_num(c["curso"]) 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 # Precomputar conversiones Google de cada campaña leadform por número de curso
leadform_conv_by_course: dict[str, int] = {} leadform_conv_by_course: dict[str, int] = {}
for c in campaigns: for c in campaigns:
@ -183,7 +193,7 @@ def run():
for campaign in campaigns: for campaign in campaigns:
cid = campaign["google_campaign_id"] 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) at.update_gacampaignmes_leads_lake(campaign["airtable_id"], lead_ids)
metrics = monthly_metrics.get(cid) metrics = monthly_metrics.get(cid)