Files
google-ads-ver-2/scripts/apply_adspro_cl4_catch_all.py
2026-05-15 09:28:11 +02:00

162 lines
5.6 KiB
Python

from __future__ import annotations
import argparse
import csv
import os
import sys
from datetime import datetime
from pathlib import Path
import requests
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.product_cl1_sales_summary import (
fetch_cl1_segments,
find_sales_csv,
read_sales,
split_products_by_top,
)
from src.gads_v2.config import load_config, load_env
from src.gads_v2.tasks.pla_cl1_sync import fetch_adspro_products
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Ustawia custom_label_4=catch_all w adsPRO dla produktow spoza top N w kazdym CL1."
)
parser.add_argument("client", help="Domena klienta, np. laitica.pl")
parser.add_argument("--top-per-cl1", type=int, default=20)
parser.add_argument("--value", default="catch_all")
parser.add_argument("--sales-csv")
parser.add_argument("--apply", action="store_true", help="Bez tej flagi zapisuje tylko plan CSV.")
parser.add_argument(
"--action",
default="product_custom_label_4_set",
help="Nazwa akcji adsPRO ustawiajacej CL4.",
)
return parser.parse_args()
def csv_cell(value: object) -> str:
text = str(value or "")
if any(char in text for char in ['"', ";", "\r", "\n"]):
return '"' + text.replace('"', '""') + '"'
return text
def write_plan(path: Path, rows: list[dict]) -> None:
headers = [
"offer_id",
"title",
"custom_label_1",
"current_custom_label_4",
"target_custom_label_4",
"conversions",
"conversion_value",
"cost",
"roas",
"status",
"message",
]
lines = [";".join(headers)]
for row in rows:
lines.append(";".join(csv_cell(row.get(header, "")) for header in headers))
path.write_text("\n".join(lines) + "\n", encoding="utf-8-sig")
def set_cl4(api_url: str, api_key: str, client_id: str, action: str, offer_id: str, value: str) -> dict:
payload = {
"action": action,
"api_key": api_key,
"client_id": client_id,
"offer_id": offer_id,
"custom_label_4": value,
}
response = requests.post(api_url, data=payload, timeout=30)
response.raise_for_status()
response.encoding = "utf-8"
try:
return response.json()
except ValueError:
return {"result": "error", "message": response.text[:500]}
def main() -> None:
args = parse_args()
load_env(ROOT / ".env")
config = load_config()
if args.client not in config.clients:
known = ", ".join(sorted(config.clients))
raise SystemExit(f"Nie znaleziono klienta {args.client}. Dostepni: {known}")
client_config = config.clients[args.client]
if not client_config.adspro_client_id:
raise SystemExit(f"Brak adspro_client_id dla {args.client}.")
sales_path = Path(args.sales_csv) if args.sales_csv else find_sales_csv(args.client)
sales = read_sales(sales_path)
segments = fetch_cl1_segments(client_config)
products = fetch_adspro_products(client_config, segments)
_, catch_all_products = split_products_by_top(products, sales, args.top_per_cl1)
rows = []
for product in catch_all_products:
offer_id = str(product.get("offer_id") or "")
stats = sales.get(offer_id, {})
rows.append(
{
"offer_id": offer_id,
"title": product.get("title", ""),
"custom_label_1": product.get("custom_label_1", ""),
"current_custom_label_4": product.get("custom_label_4", ""),
"target_custom_label_4": args.value,
"conversions": str(product.get("conversions", stats.get("conversions", 0))),
"conversion_value": str(product.get("conversion_value", stats.get("conversion_value", 0))),
"cost": str(product.get("cost", stats.get("cost", 0))),
"roas": str(product.get("roas", stats.get("roas", 0))),
"status": "planned",
"message": "",
}
)
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
plan_path = ROOT / "clients" / args.client / "data" / f"adspro_cl4_catch_all_plan_{timestamp}.csv"
plan_path.parent.mkdir(parents=True, exist_ok=True)
if args.apply:
api_url = os.environ.get("ADSPRO_API_URL")
api_key = os.environ.get("ADSPRO_API_KEY")
if not api_url or not api_key:
raise SystemExit("Brak ADSPRO_API_URL lub ADSPRO_API_KEY w .env.")
for index, row in enumerate(rows, 1):
result = set_cl4(
api_url,
api_key,
client_config.adspro_client_id,
args.action,
row["offer_id"],
args.value,
)
if result.get("result") == "error":
row["status"] = "error"
row["message"] = result.get("message", "")
print(f"{index}/{len(rows)} ERROR {row['offer_id']}: {row['message']}", flush=True)
else:
row["status"] = "updated"
row["message"] = result.get("message", "")
if index % 50 == 0 or index == len(rows):
print(f"{index}/{len(rows)} zaktualizowano", flush=True)
write_plan(plan_path, rows)
updated = sum(1 for row in rows if row["status"] == "updated")
errors = sum(1 for row in rows if row["status"] == "error")
print(f"Produkty do CL4={args.value}: {len(rows)}")
print(f"Zaktualizowano: {updated}")
print(f"Bledy: {errors}")
print(f"Raport: {plan_path}")
if __name__ == "__main__":
main()