← back to Designer Wallcoverings

onboarding/sangetsu-lilycolor/scripts/sangetsu-colorway-recover.py

84 lines

#!/usr/bin/env python3
"""Sangetsu colorway_skus recovery from the free WooCommerce Store API.

The sitemap/page scraper left 238/623 patterns with ZERO colorway_skus (overview
only) and mis-extracted ~46 thin pages (grabbed one stray cross-listed SKU). The
Store API product object carries the authoritative colorway list under
  attributes[] -> (has_variations | name~="collection") -> terms[].name
Validated 3/3 against patterns whose colorway_skus were already known (exact match,
0 known-only / 0 api-only), so the API is authoritative.

Rule: overwrite colorway_skus with the API extraction ONLY when the API returns a
non-empty list (never wipe good data on an API miss). Idempotent. $0, read-only
fetch + atomic rewrite. No dw_unified / Shopify write.
"""
import json, os, sys, time

HERE = os.path.dirname(os.path.abspath(__file__))
PROJ = os.path.dirname(HERE)
STAGING = os.path.join(PROJ, "staging", "sangetsu-staging.jsonl")
sys.path.insert(0, os.path.expanduser("~/Projects/japan-enrich"))
from scrape_sangetsu_specs import get, slug_of  # noqa: E402


def extract_colorways(prod):
    out, seen = [], set()
    for a in (prod.get("attributes") or []):
        if a.get("has_variations") or "collection" in (a.get("name", "").lower()):
            for t in (a.get("terms") or []):
                nm = (t.get("name") or "").strip()
                if nm and nm not in seen:
                    seen.add(nm)
                    out.append(nm)
    return out


def main():
    rows = [json.loads(l) for l in open(STAGING) if l.strip()]
    limit = int(sys.argv[sys.argv.index("--limit") + 1]) if "--limit" in sys.argv else len(rows)
    empty_before = sum(1 for r in rows if not (r.get("colorway_skus") or []))
    total_before = sum(len(r.get("colorway_skus") or []) for r in rows)

    recovered = filled = replaced = miss = 0
    for i, r in enumerate(rows[:limit]):
        d = get(f"/products?slug={slug_of(r.get('source_url'))}")
        if not d:
            miss += 1
            continue
        api = extract_colorways(d[0])
        if not api:
            continue
        old = [str(x).strip() for x in (r.get("colorway_skus") or [])]
        if set(x.upper() for x in api) == set(x.upper() for x in old):
            pass  # already correct
        else:
            if not old:
                filled += 1
            else:
                replaced += 1
            r["colorway_skus"] = api
            r["colorway_count"] = len(api)
            recovered += 1
        if i % 50 == 0:
            print(f"[{i+1}/{limit}] {r.get('pattern')}: api={len(api)} old={len(old)}", flush=True)
        time.sleep(0.15)

    tmp = STAGING + ".tmp"
    with open(tmp, "w") as f:
        for r in rows:
            f.write(json.dumps(r, ensure_ascii=False) + "\n")
    os.replace(tmp, STAGING)

    empty_after = sum(1 for r in rows if not (r.get("colorway_skus") or []))
    total_after = sum(len(r.get("colorway_skus") or []) for r in rows)
    print(json.dumps({
        "patterns": len(rows), "store_api_miss": miss,
        "changed": recovered, "empty_filled": filled, "thin_replaced": replaced,
        "empty_patterns": f"{empty_before} -> {empty_after}",
        "total_colorway_skus": f"{total_before} -> {total_after}",
    }, indent=2))


if __name__ == "__main__":
    main()