162 lines
5.6 KiB
Python
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()
|