← back to Hollywood Price 2026
build_524_split.py
98 lines
#!/usr/bin/env python3
"""Split the 524 'absent' Needs-Price Hollywood products (Steve 2026-07-12):
- PRICE the ones still live on Momentum (col3 pattern-prefix resolves to a live
Momentum pattern WITH a price) -> plan_169.csv (price = pattern price x 1.448).
- ARCHIVE the rest (no live Momentum pattern match) -> archive_355.csv.
Pattern-level pricing is valid (Momentum prices are per-pattern, verified). $0."""
import sys
import json
import os
import urllib.request
import re
import csv
sys.path.insert(0, "lib")
from shopify import gql
HI = os.path.expanduser("~/Projects/hollywood-import")
KEY = "95fe8376edc78d49e787db49edda68426b21649f6271f49e2f1412118612fbd6"
HOST = "https://ms-e886719d86e7-4256.sfo.meilisearch.io"
MARKUP = 1.448
def live_patterns():
out = {}; off = 0
while off < 20000:
req = urllib.request.Request(f"{HOST}/indexes/redesign-colors/search",
data=json.dumps({"q": "", "limit": 1000, "offset": off,
"attributesToRetrieve": ["pattern_name", "price", "UOM"]}).encode(), method="POST",
headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
hits = json.load(urllib.request.urlopen(req, timeout=40)).get("hits", [])
if not hits:
break
for h in hits:
pn = (h.get("pattern_name") or "").strip().lower()
if pn and h.get("price"):
out[pn] = {"price": h["price"], "uom": h.get("UOM")}
off += len(hits)
if len(hits) < 1000:
break
return out
def col3_pattern(c3):
return re.split(r'[_-]?(sg|a|bx|l2|nv|hi|mi|nfr)?[\d]', c3 or "", flags=re.I)[0].replace("_", " ").strip().lower()
def main():
absent = {str(r["mfr"]).strip(): r for r in json.load(open(HI + "/mom-col3-absent.json")) if r.get("mfr")}
staged = {str(r["mfr"]).strip() for r in json.load(open(HI + "/mom-col3-staged.json")) if r.get("mfr")}
lp = live_patterns()
print(f"live Momentum patterns w/ price: {len(lp)}")
Q = """query($q:String!,$after:String){ products(first:200, after:$after, query:$q){ pageInfo{hasNextPage endCursor}
edges{ node{ id legacyResourceId title variants(first:5){ edges{ node{ sku } } } } } } }"""
prods, cur = [], None
while True:
d = gql(Q, {"q": "vendor:'Hollywood Wallcoverings' status:active tag:Needs-Price", "after": cur})
p = d["products"]; prods += [e["node"] for e in p["edges"]]
if not p["pageInfo"]["hasNextPage"]:
break
cur = p["pageInfo"]["endCursor"]
def base(n):
for e in n["variants"]["edges"]:
sk = (e["node"]["sku"] or "")
if sk.lower().endswith("-sample"):
return sk[:-len("-sample")]
return None
price_rows, arch_rows = [], []
for n in prods:
sb = base(n)
if not sb or sb not in absent or sb in staged:
continue
c3 = absent[sb].get("col3", "") or ""
pat = col3_pattern(c3)
# confident match: normalized col3-pattern EXACTLY equals a live pattern name
rec = lp.get(pat) if len(pat) > 3 else None
if rec and (rec.get("uom") in (None, "YD", "SY")):
price_rows.append({"gid": n["id"], "product_id": n["legacyResourceId"], "title": n["title"],
"sku_base": sb, "unit": (rec.get("uom") or "YD"),
"hw": round(rec["price"] * MARKUP, 2), "list": rec["price"],
"mom_pattern": pat, "col3": c3})
else:
arch_rows.append({"gid": n["id"], "product_id": n["legacyResourceId"], "title": n["title"],
"sku_base": sb, "col3": c3, "dwPattern": absent[sb].get("dwPattern")})
for fn, rows in [("plan_169.csv", price_rows), ("archive_355.csv", arch_rows)]:
if rows:
with open(fn, "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=list(rows[0].keys())); w.writeheader(); w.writerows(rows)
print(f"PRICE-LIVE (exact pattern match): {len(price_rows)} -> plan_169.csv")
print(f"ARCHIVE (no live match): {len(arch_rows)} -> archive_355.csv")
if price_rows:
for r in price_rows[:6]:
print(f" price: {r['title'][:34]:34} {r['mom_pattern']!r:14} list ${r['list']} -> hw ${r['hw']}")
if __name__ == "__main__":
main()