This commit is contained in:
2026-05-15 23:19:26 +02:00
parent def1fae0fc
commit 75b9434de5
113 changed files with 50906 additions and 1305 deletions

View File

@@ -116,6 +116,15 @@ def normalize_text(value: str) -> str:
)
def ad_group_name_for_product(product: dict, duplicate_title: bool = False) -> str:
title = (product.get("title") or "").strip()
offer_id = (product.get("offer_id") or "").strip()
if not duplicate_title or not offer_id:
return title
suffix = f" | {offer_id}"
return f"{title[:255 - len(suffix)]}{suffix}"
def parse_allowed_labels(campaign_name: str) -> set[str]:
match = re.search(r"\]\s*(.+)$", campaign_name)
raw = match.group(1).strip() if match else campaign_name
@@ -128,6 +137,10 @@ def normalize_variant(value: str) -> str:
return (value or "").strip().lower()
def product_has_paused_cl4(product: dict) -> bool:
return normalize_variant(product.get("custom_label_4")) == "paused"
def parse_campaign_variant(campaign_name: str) -> str:
"""Wariant kampanii z czesci po znaku '|' w nazwie, np. 'catch_all'.
@@ -335,6 +348,7 @@ def build_plan(client, customer_id: str, products: list[dict]) -> SyncPlan:
products_by_campaign = defaultdict(list)
fallback_offers = []
orphan_offers = []
paused_offers = []
for product in products:
offer_id = (product.get("offer_id") or "").strip()
title = (product.get("title") or "").strip()
@@ -345,6 +359,9 @@ def build_plan(client, customer_id: str, products: list[dict]) -> SyncPlan:
by_title_norm[normalize_text(title)].append(product)
if not (label and title and offer_id):
continue
if product_has_paused_cl4(product):
paused_offers.append(offer_id)
continue
target, used_fallback = resolve_target_campaign(product)
if target is None:
orphan_offers.append(offer_id)
@@ -413,7 +430,16 @@ def build_plan(client, customer_id: str, products: list[dict]) -> SyncPlan:
if record["ad_group_status"] == "ENABLED":
enabled_offers_by_campaign[record["campaign_id"]].add(record["offer_id"])
title_counts_by_campaign = defaultdict(Counter)
for campaign_id, campaign_products in products_by_campaign.items():
title_counts_by_campaign[campaign_id].update(
normalize_text(product.get("title") or "")
for product in campaign_products
if (product.get("title") or "").strip()
)
wrong_groups = []
paused_product_groups = []
groups_without_match = []
active_groups_without_match = []
rename_plan = []
@@ -422,7 +448,7 @@ def build_plan(client, customer_id: str, products: list[dict]) -> SyncPlan:
match_via = "offer_id" if product else None
if not product:
candidates = by_title_norm.get(normalize_text(group["ad_group_name"])) or []
if candidates:
if len(candidates) == 1:
product = candidates[0]
match_via = "title"
if not product:
@@ -430,6 +456,10 @@ def build_plan(client, customer_id: str, products: list[dict]) -> SyncPlan:
if group["ad_group_status"] == "ENABLED":
active_groups_without_match.append(group)
continue
if product_has_paused_cl4(product):
if group["ad_group_status"] == "ENABLED":
paused_product_groups.append((group, product))
continue
label = (product.get("custom_label_1") or "").strip()
if not label:
if group["ad_group_status"] == "ENABLED":
@@ -445,7 +475,11 @@ def build_plan(client, customer_id: str, products: list[dict]) -> SyncPlan:
# Zla kampania albo zly wariant (np. CL4 puste, a grupa w kampanii | catch_all).
wrong_groups.append((group, product))
continue
adspro_title = (product.get("title") or "").strip()
title_key = normalize_text(product.get("title") or "")
duplicate_title = False
if target and title_key:
duplicate_title = title_counts_by_campaign[target["id"]][title_key] > 1
adspro_title = ad_group_name_for_product(product, duplicate_title)
if (
group["ad_group_status"] == "ENABLED"
and match_via == "offer_id"
@@ -468,7 +502,9 @@ def build_plan(client, customer_id: str, products: list[dict]) -> SyncPlan:
def plan_enable_or_create(campaign: dict, product: dict, fallback_name: str, reason: str) -> None:
offer_id = (product.get("offer_id") or "").strip()
title = (product.get("title") or "").strip() or fallback_name
title_key = normalize_text(product.get("title") or "")
duplicate_title = title_key and title_counts_by_campaign[campaign["id"]][title_key] > 1
title = ad_group_name_for_product(product, bool(duplicate_title)) or fallback_name
if not offer_id or not title:
return
if offer_id in enabled_offers_by_campaign[campaign["id"]]:
@@ -477,11 +513,13 @@ def build_plan(client, customer_id: str, products: list[dict]) -> SyncPlan:
# Najpierw szukamy grupy reklam po produkcie (offer_id), a dopiero
# potem po nazwie. Dzieki temu istniejaca grupa z tym produktem, ale
# pod inna nazwa, zostanie wlaczona i przemianowana, a nie zduplikowana.
# Przy zduplikowanych tytulach nie dopasowujemy po samej nazwie, bo jedna
# grupa reklam moze wtedy blednie pokryc kilka roznych offer_id.
matched_via = None
existing_candidates = existing_groups_by_campaign_offer.get((campaign["id"], offer_id), [])
if existing_candidates:
matched_via = "offer_id"
else:
elif not duplicate_title:
existing_candidates = existing_groups_by_campaign_name.get((campaign["id"], normalize_text(title)), [])
if existing_candidates:
matched_via = "title"
@@ -530,6 +568,15 @@ def build_plan(client, customer_id: str, products: list[dict]) -> SyncPlan:
wrong_variant_count = 0
wrong_segment_count = 0
for group, product in paused_product_groups:
pause_by_id[group["ad_group_id"]] = {
"ad_group_id": group["ad_group_id"],
"ad_group_name": group["ad_group_name"],
"campaign_id": group["campaign_id"],
"campaign_name": group["campaign_name"],
"reason": "produkt ma CL4=paused w adsPRO",
}
for group, product in wrong_groups:
offer_id = (product.get("offer_id") or "").strip()
target = target_by_offer.get(offer_id)
@@ -616,6 +663,16 @@ def build_plan(client, customer_id: str, products: list[dict]) -> SyncPlan:
f"Produkty z CL4, ale bez kampanii-wariantu, przypisane do kampanii bazowej: "
f"{len(fallback_offers)}."
)
if paused_offers:
warnings.append(
f"Produkty z CL4=paused pominiete przy tworzeniu i wlaczaniu grup reklam: "
f"{len(paused_offers)}."
)
if paused_product_groups:
warnings.append(
f"Aktywne grupy reklam produktow z CL4=paused do wstrzymania: "
f"{len(paused_product_groups)}."
)
if orphan_offers:
warnings.append(
f"Produkty bez pasujacej kampanii PLA_CL1 (CL1/CL4): {len(orphan_offers)}."