← back to Elitis Price 2026

exec/ws2_archive.py

88 lines

#!/usr/bin/env python3
"""WS2 — archive the 20 off-list disco Elitis products (VP-1048 + VP-1050).
Discontinued = ARCHIVE (never draft, never delete). Idempotent + resumable.
$0 (Shopify Admin API is free)."""
import csv, sys, os, json, datetime, time
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "lib"))
from shopify import gql, find_product_by_title

LEDGER = os.path.join(os.path.dirname(__file__), "ledger", "ws2_archived.csv")
PLAN = os.path.join(os.path.dirname(__file__), "..", "PLAN_price_update.csv")

M_ARCHIVE = """mutation($input:ProductInput!){
  productUpdate(input:$input){ userErrors{ field message }
    product{ id status title } } }"""


def append_ledger(row):
    exists = os.path.exists(LEDGER) and os.path.getsize(LEDGER) > 0
    with open(LEDGER, "a", newline="") as f:
        w = csv.writer(f)
        if not exists:
            w.writerow(["ts", "title", "product_id", "prev_status", "new_status"])
        w.writerow(row)


def seen():
    s = set()
    if os.path.exists(LEDGER):
        for r in csv.DictReader(open(LEDGER)):
            if r.get("new_status") in ("ARCHIVED", "already-ARCHIVED"):
                s.add(r["title"])
    return s


def process(r):
    ts = datetime.datetime.now().isoformat(timespec="seconds")
    title = r["title"]
    node = find_product_by_title(title)
    if not node:
        append_ledger([ts, title, "", "", "NOT-FOUND"])
        return "NOT-FOUND"
    pid = str(node["legacyResourceId"]); prev = node["status"]
    if prev == "ARCHIVED":
        append_ledger([ts, title, pid, prev, "already-ARCHIVED"])
        return "already-ARCHIVED"
    d = gql(M_ARCHIVE, {"input": {"id": node["id"], "status": "ARCHIVED"}})
    ue = d["productUpdate"]["userErrors"]
    if ue:
        append_ledger([ts, title, pid, prev, "FAIL:" + json.dumps(ue)[:120]])
        return "FAIL"
    new = d["productUpdate"]["product"]["status"]
    append_ledger([ts, title, pid, prev, new])
    return new


if __name__ == "__main__":
    plan = list(csv.DictReader(open(PLAN)))
    disco = [r for r in plan if r["action"].startswith("DISCONTINUE")]
    done = seen()
    canary = "--canary" in sys.argv
    if canary:
        r = disco[0]
        print("=== WS2 CANARY archive:", r["title"], "===")
        # verify before
        n = find_product_by_title(r["title"])
        print("BEFORE status:", n["status"] if n else "NOT-FOUND", "| id:",
              n["legacyResourceId"] if n else None)
        st = process(r)
        n2 = find_product_by_title(r["title"])
        print("AFTER status:", n2["status"] if n2 else "NOT-FOUND")
        print("RESULT:", st)
        sys.exit(0)
    n_arch = n_already = n_fail = n_missing = 0
    for r in disco:
        if r["title"] in done:
            n_already += 1; continue
        st = process(r)
        if st == "ARCHIVED":
            n_arch += 1
        elif st == "already-ARCHIVED":
            n_already += 1
        elif st == "NOT-FOUND":
            n_missing += 1
        else:
            n_fail += 1
        time.sleep(0.15)
    print(f"WS2 DONE: archived={n_arch} already={n_already} missing={n_missing} fail={n_fail} total={len(disco)}")