← back to Hollywood Price 2026
quote-only executor for the ~258 unpriceable Hollywood leftovers (Versa commercial has no public price)
fd8fbf83e8821f40eed8af7be0f99e29848e1e9b · 2026-07-12 09:51:02 -0700 · Steve Abrams
Files touched
Diff
commit fd8fbf83e8821f40eed8af7be0f99e29848e1e9b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jul 12 09:51:02 2026 -0700
quote-only executor for the ~258 unpriceable Hollywood leftovers (Versa commercial has no public price)
---
quote_only.py | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 91 insertions(+)
diff --git a/quote_only.py b/quote_only.py
new file mode 100644
index 0000000..67841f2
--- /dev/null
+++ b/quote_only.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+"""Quote-only the remaining unpriceable Hollywood products (Steve 2026-07-12).
+After pricing (856) + archiving (267), the leftover ACTIVE + Needs-Price Hollywood
+set is the ~246 unmatched + ~12 no-sku — no resolvable Momentum/Versa price
+(Versa commercial publishes none). Mark them quote-only: ADD `quotes` tag, REMOVE
+`Needs-Price`. Keeps active + Sample-only, no fabricated price. Reversible. $0."""
+import sys, os, csv, datetime, time
+sys.path.insert(0, "lib")
+from shopify import gql
+
+LEDGER = os.path.join("ledger", "quote_only.csv")
+M_ADD = """mutation($id:ID!,$tags:[String!]!){ tagsAdd(id:$id, tags:$tags){ userErrors{ message } } }"""
+M_RM = """mutation($id:ID!,$tags:[String!]!){ tagsRemove(id:$id, tags:$tags){ userErrors{ message } } }"""
+Q = """query($q:String!,$after:String){ products(first:150, after:$after, query:$q){
+ pageInfo{hasNextPage endCursor}
+ edges{ node{ id legacyResourceId title tags
+ variants(first:5){ edges{ node{ sku title } } } } } } }"""
+SEARCH = "vendor:'Hollywood Wallcoverings' status:active tag:Needs-Price"
+
+
+def has_sellable(n):
+ for e in n["variants"]["edges"]:
+ v = e["node"]; sk = (v["sku"] or "")
+ if v["title"] != "Sample" and not sk.lower().endswith("-sample"):
+ return True
+ return False
+
+
+def log(pid, title, status):
+ new = not (os.path.exists(LEDGER) and os.path.getsize(LEDGER) > 0)
+ with open(LEDGER, "a", newline="") as f:
+ w = csv.writer(f)
+ if new:
+ w.writerow(["ts", "product_id", "title", "status"])
+ w.writerow([datetime.datetime.now().isoformat(timespec="seconds"), pid, title, status])
+
+
+def seen():
+ s = set()
+ if os.path.exists(LEDGER):
+ for r in csv.DictReader(open(LEDGER)):
+ if r["status"].startswith("OK") and r.get("product_id"):
+ s.add(r["product_id"])
+ return s
+
+
+def fetch_all():
+ out, cur = [], None
+ while True:
+ d = gql(Q, {"q": SEARCH, "after": cur})
+ p = d["products"]; out += [e["node"] for e in p["edges"]]
+ if not p["pageInfo"]["hasNextPage"]:
+ break
+ cur = p["pageInfo"]["endCursor"]
+ return out
+
+
+def process(n):
+ pid = str(n["legacyResourceId"]); gid = n["id"]
+ # safety: never quote-only a product that actually has a real sellable variant
+ if has_sellable(n):
+ log(pid, n["title"], "SKIP:has-sellable"); return "SKIP:has-sellable"
+ if "quotes" in n["tags"] and "Needs-Price" not in n["tags"]:
+ log(pid, n["title"], "SKIP:already-quote-only"); return "SKIP:already"
+ a = gql(M_ADD, {"id": gid, "tags": ["quotes"]})["tagsAdd"]["userErrors"]
+ r = gql(M_RM, {"id": gid, "tags": ["Needs-Price"]})["tagsRemove"]["userErrors"]
+ if a or r:
+ log(pid, n["title"], "FAIL:" + str(a or r)[:80]); return "FAIL"
+ log(pid, n["title"], "OK"); return "OK"
+
+
+if __name__ == "__main__":
+ nodes = fetch_all()
+ print(f"remaining ACTIVE Needs-Price Hollywood: {len(nodes)}")
+ if "--canary" in sys.argv:
+ k = int(sys.argv[sys.argv.index("--canary") + 1])
+ for n in nodes[:k]:
+ print(f" {n['title'][:44]:44} -> {process(n)}")
+ sys.exit(0)
+ done = seen()
+ todo = [n for n in nodes if str(n["legacyResourceId"]) not in done]
+ ok = sk = fl = 0
+ for i, n in enumerate(todo):
+ st = process(n)
+ if st == "OK": ok += 1
+ elif st.startswith("FAIL"): fl += 1
+ else: sk += 1
+ if (i + 1) % 40 == 0:
+ print(f" ...{i+1}/{len(todo)} ok={ok} skip={sk} fail={fl}")
+ time.sleep(0.1)
+ print(f"QUOTE-ONLY DONE: ok={ok} skip={sk} fail={fl}")
← d602474 auto-save: 2026-07-12T09:47:28 (1 files) — unmatched_246.jso
·
back to Hollywood Price 2026
·
quote-only 258 unpriceable Hollywood leftovers (quotes tag + 11758ed →