← back to Designer Wallcoverings
Tier B packaging answered via variant-title scan: per-vendor MOQ determinations + evidence
99e771635b5eaa739cdbe6af869e9ad97987ab3e · 2026-06-22 14:28:07 -0700 · Steve Abrams
Files touched
A shopify/scripts/moq/scan-tierB-variant-titles.pyA shopify/scripts/moq/tierB-answers.jsonA shopify/scripts/moq/tierB-variant-classes.json
Diff
commit 99e771635b5eaa739cdbe6af869e9ad97987ab3e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jun 22 14:28:07 2026 -0700
Tier B packaging answered via variant-title scan: per-vendor MOQ determinations + evidence
---
shopify/scripts/moq/scan-tierB-variant-titles.py | 48 +++
shopify/scripts/moq/tierB-answers.json | 402 +++++++++++++++++++++++
shopify/scripts/moq/tierB-variant-classes.json | 346 +++++++++++++++++++
3 files changed, 796 insertions(+)
diff --git a/shopify/scripts/moq/scan-tierB-variant-titles.py b/shopify/scripts/moq/scan-tierB-variant-titles.py
new file mode 100644
index 00000000..aff8fe10
--- /dev/null
+++ b/shopify/scripts/moq/scan-tierB-variant-titles.py
@@ -0,0 +1,48 @@
+import json,glob,os,urllib.request,time,re
+from collections import Counter,defaultdict
+TOKEN=os.popen("grep -E '^SHOPIFY_ADMIN_TOKEN=' .env | cut -d= -f2").read().strip()
+API="https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-01/graphql.json"
+def gql(q):
+ for a in range(4):
+ try:
+ req=urllib.request.Request(API,data=json.dumps({"query":q}).encode(),
+ headers={"X-Shopify-Access-Token":TOKEN,"Content-Type":"application/json"})
+ return json.load(urllib.request.urlopen(req))
+ except Exception as e: time.sleep(1.5*(a+1)); err=str(e)
+ return {"errors":err}
+def classify(t):
+ t=t.lower()
+ if 'double roll' in t: return 'double_roll'
+ if 'single roll' in t: return 'single_roll'
+ if 'roll' in t: return 'roll_other'
+ if re.search(r'\b(yard|yd|yds)\b',t): return 'per_yard'
+ if any(k in t for k in ['panel','mural','set','sq ft','square f','sqft']): return 'panel_mural'
+ if 'sample' in t: return 'sample'
+ if any(k in t for k in ['meter','metre']): return 'per_meter'
+ return 'other:'+t[:24]
+os.chdir('shopify/scripts/moq/tierB')
+rows=defaultdict(Counter)
+for f in sorted(glob.glob('*.todo.json')):
+ items=json.load(open(f))
+ ids=[it['id'] for it in items][:40] # sample up to 40/vendor
+ vendor=items[0]['vendor'] if items else f
+ for i in range(0,len(ids),20):
+ chunk=ids[i:i+20]
+ frag=" ".join('p%d:product(id:"%s"){ variants(first:8){edges{node{title}}} }'%(j,pid) for j,pid in enumerate(chunk))
+ d=gql("{ %s }"%frag)
+ if 'errors' in d: continue
+ for j in range(len(chunk)):
+ n=d['data'].get('p%d'%j) or {}
+ for e in n.get('variants',{}).get('edges',[]):
+ c=classify(e['node']['title'])
+ if c!='sample': rows[vendor][c]+=1
+ time.sleep(0.2)
+out={}
+for v,c in rows.items():
+ total=sum(c.values()) or 1
+ top=c.most_common(3)
+ out[v]={'sampled_nonsample_variants':total,'top':[(k,round(100*n/total)) for k,n in top]}
+json.dump(out,open('/tmp/tierB-variant-classes.json','w'),indent=1)
+print("vendor | dominant non-sample variant packaging (%)")
+for v in sorted(out, key=lambda x:-out[x]['sampled_nonsample_variants']):
+ o=out[v]; print(" %-26s %s"%(v[:26], ' '.join('%s=%d%%'%(k,p) for k,p in o['top'])))
diff --git a/shopify/scripts/moq/tierB-answers.json b/shopify/scripts/moq/tierB-answers.json
new file mode 100644
index 00000000..ee68c30d
--- /dev/null
+++ b/shopify/scripts/moq/tierB-answers.json
@@ -0,0 +1,402 @@
+{
+ "1838 Wallcoverings": {
+ "moq": "per-product",
+ "why": "mixed \"sold per roll\" + single roll \u2014 classify per variant title",
+ "conf": "med",
+ "evidence": [
+ [
+ "roll_other",
+ 74
+ ],
+ [
+ "other:default title",
+ 26
+ ]
+ ]
+ },
+ "Anna French": {
+ "moq": "2/2",
+ "why": "residential single roll \u2014 double-roll bolt, order in 2s",
+ "conf": "high",
+ "evidence": [
+ [
+ "single_roll",
+ 75
+ ],
+ [
+ "per_yard",
+ 25
+ ]
+ ]
+ },
+ "Brunschwig & Fils": {
+ "moq": "2/1",
+ "why": "fabric \u2014 DW 2-yard min",
+ "conf": "high",
+ "evidence": [
+ [
+ "per_yard",
+ 100
+ ]
+ ]
+ },
+ "Clarke And Clarke": {
+ "moq": "2/1",
+ "why": "fabric \u2014 DW 2-yard min",
+ "conf": "high",
+ "evidence": [
+ [
+ "per_yard",
+ 50
+ ],
+ [
+ "other:default title",
+ 50
+ ]
+ ]
+ },
+ "Cole & Son": {
+ "moq": "1/1",
+ "why": "sold per bolt \u2014 bolt IS the unit",
+ "conf": "high",
+ "evidence": [
+ [
+ "other:sold per bolt (20.5in x ",
+ 100
+ ]
+ ]
+ },
+ "DW Bespoke Studio": {
+ "moq": "per-product",
+ "why": "mixed/unclear \u2014 per-product classify",
+ "conf": "low",
+ "evidence": [
+ [
+ "other:gold textured metallic /",
+ 40
+ ],
+ [
+ "other:peel and stick / 2ft x 1",
+ 20
+ ],
+ [
+ "other:prepasted / 2ft x 12ft",
+ 20
+ ]
+ ]
+ },
+ "Designer Wallcoverings": {
+ "moq": "1/1",
+ "why": "no packaging signal \u2014 leave as-is",
+ "conf": "low",
+ "evidence": [
+ [
+ "other:default title",
+ 16
+ ],
+ [
+ "other:x-small",
+ 3
+ ],
+ [
+ "other:x-large",
+ 3
+ ]
+ ]
+ },
+ "Designtex": {
+ "moq": "1/1",
+ "why": "commercial single-roll, no double-roll convention",
+ "conf": "med",
+ "evidence": [
+ [
+ "single_roll",
+ 75
+ ],
+ [
+ "roll_other",
+ 22
+ ],
+ [
+ "per_yard",
+ 2
+ ]
+ ]
+ },
+ "GP & J Baker": {
+ "moq": "2/1",
+ "why": "fabric \u2014 DW 2-yard min",
+ "conf": "high",
+ "evidence": [
+ [
+ "per_yard",
+ 100
+ ]
+ ]
+ },
+ "Gaston Y Daniela": {
+ "moq": "2/1",
+ "why": "fabric \u2014 DW 2-yard min",
+ "conf": "high",
+ "evidence": [
+ [
+ "per_yard",
+ 100
+ ]
+ ]
+ },
+ "Graham & Brown": {
+ "moq": "1/1",
+ "why": "no packaging signal \u2014 leave as-is",
+ "conf": "low",
+ "evidence": [
+ [
+ "other:default title",
+ 89
+ ],
+ [
+ "roll_other",
+ 11
+ ]
+ ]
+ },
+ "Innovations USA": {
+ "moq": "2/1",
+ "why": "fabric \u2014 DW 2-yard min",
+ "conf": "high",
+ "evidence": [
+ [
+ "per_yard",
+ 73
+ ],
+ [
+ "single_roll",
+ 27
+ ]
+ ]
+ },
+ "Kravet": {
+ "moq": "2/1",
+ "why": "fabric \u2014 DW 2-yard min",
+ "conf": "high",
+ "evidence": [
+ [
+ "per_yard",
+ 100
+ ]
+ ]
+ },
+ "Lee Jofa": {
+ "moq": "2/1",
+ "why": "fabric \u2014 DW 2-yard min",
+ "conf": "high",
+ "evidence": [
+ [
+ "per_yard",
+ 100
+ ]
+ ]
+ },
+ "Malibu Wallpaper": {
+ "moq": "1/1",
+ "why": "no packaging signal \u2014 leave as-is",
+ "conf": "low",
+ "evidence": [
+ [
+ "other:standard",
+ 90
+ ],
+ [
+ "single_roll",
+ 10
+ ]
+ ]
+ },
+ "Mulberry": {
+ "moq": "2/1",
+ "why": "fabric \u2014 DW 2-yard min",
+ "conf": "high",
+ "evidence": [
+ [
+ "per_yard",
+ 100
+ ]
+ ]
+ },
+ "Newwall": {
+ "moq": "2/2",
+ "why": "residential single roll \u2014 double-roll bolt, order in 2s",
+ "conf": "high",
+ "evidence": [
+ [
+ "single_roll",
+ 100
+ ]
+ ]
+ },
+ "Phillipe Romano": {
+ "moq": "1/1",
+ "why": "no packaging signal \u2014 leave as-is",
+ "conf": "low",
+ "evidence": [
+ [
+ "other:default title",
+ 100
+ ]
+ ]
+ },
+ "Pixels": {
+ "moq": "1/1",
+ "why": "tile/piece sets \u2014 unit good",
+ "conf": "high",
+ "evidence": [
+ [
+ "other:12\" x 16\" (96 pieces)",
+ 4
+ ],
+ [
+ "other:12\" x 16\" (252 pieces)",
+ 4
+ ],
+ [
+ "other:18\" x 24\" (500 pieces)",
+ 4
+ ]
+ ]
+ },
+ "Rebel Walls": {
+ "moq": "1/1",
+ "why": "murals/panels/sets \u2014 unit good",
+ "conf": "high",
+ "evidence": [
+ [
+ "panel_mural",
+ 95
+ ],
+ [
+ "other:default title",
+ 5
+ ]
+ ]
+ },
+ "Romo": {
+ "moq": "2/2",
+ "why": "residential single roll \u2014 double-roll bolt, order in 2s",
+ "conf": "high",
+ "evidence": [
+ [
+ "single_roll",
+ 60
+ ],
+ [
+ "other:default title",
+ 20
+ ],
+ [
+ "roll_other",
+ 20
+ ]
+ ]
+ },
+ "Ronald Redding": {
+ "moq": "1/1",
+ "why": "no packaging signal \u2014 leave as-is",
+ "conf": "low",
+ "evidence": [
+ [
+ "other:default title",
+ 100
+ ]
+ ]
+ },
+ "Sandberg": {
+ "moq": "1/1",
+ "why": "sold per bolt \u2014 bolt IS the unit",
+ "conf": "high",
+ "evidence": [
+ [
+ "other:sold per bolt (20.5in x ",
+ 100
+ ]
+ ]
+ },
+ "Schumacher": {
+ "moq": "per-product",
+ "why": "mixed \"sold per roll\" + single roll \u2014 classify per variant title",
+ "conf": "med",
+ "evidence": [
+ [
+ "roll_other",
+ 68
+ ],
+ [
+ "single_roll",
+ 32
+ ]
+ ]
+ },
+ "Thibaut": {
+ "moq": "2/2",
+ "why": "residential single roll \u2014 double-roll bolt, order in 2s",
+ "conf": "high",
+ "evidence": [
+ [
+ "single_roll",
+ 95
+ ],
+ [
+ "other:default title",
+ 5
+ ]
+ ]
+ },
+ "Tres Tintas": {
+ "moq": "2/1",
+ "why": "sold per metre \u2014 2-unit min (metre analog)",
+ "conf": "med",
+ "evidence": [
+ [
+ "per_meter",
+ 52
+ ],
+ [
+ "roll_other",
+ 48
+ ]
+ ]
+ },
+ "Versace": {
+ "moq": "2/2",
+ "why": "residential single roll \u2014 double-roll bolt, order in 2s",
+ "conf": "high",
+ "evidence": [
+ [
+ "single_roll",
+ 88
+ ],
+ [
+ "other:standard",
+ 12
+ ]
+ ]
+ },
+ "William Morris": {
+ "moq": "1/1",
+ "why": "sold per bolt \u2014 bolt IS the unit",
+ "conf": "high",
+ "evidence": [
+ [
+ "other:bolt 20.28in x 33ft",
+ 82
+ ],
+ [
+ "other:bolt 0in x 33ft",
+ 10
+ ],
+ [
+ "other:bolt",
+ 8
+ ]
+ ]
+ }
+}
\ No newline at end of file
diff --git a/shopify/scripts/moq/tierB-variant-classes.json b/shopify/scripts/moq/tierB-variant-classes.json
new file mode 100644
index 00000000..ea7929b1
--- /dev/null
+++ b/shopify/scripts/moq/tierB-variant-classes.json
@@ -0,0 +1,346 @@
+{
+ "1838 Wallcoverings": {
+ "sampled_nonsample_variants": 35,
+ "top": [
+ [
+ "roll_other",
+ 74
+ ],
+ [
+ "other:default title",
+ 26
+ ]
+ ]
+ },
+ "Anna French": {
+ "sampled_nonsample_variants": 40,
+ "top": [
+ [
+ "single_roll",
+ 75
+ ],
+ [
+ "per_yard",
+ 25
+ ]
+ ]
+ },
+ "Brunschwig & Fils": {
+ "sampled_nonsample_variants": 40,
+ "top": [
+ [
+ "per_yard",
+ 100
+ ]
+ ]
+ },
+ "Clarke And Clarke": {
+ "sampled_nonsample_variants": 80,
+ "top": [
+ [
+ "per_yard",
+ 50
+ ],
+ [
+ "other:default title",
+ 50
+ ]
+ ]
+ },
+ "Cole & Son": {
+ "sampled_nonsample_variants": 40,
+ "top": [
+ [
+ "other:sold per bolt (20.5in x ",
+ 100
+ ]
+ ]
+ },
+ "Designer Wallcoverings": {
+ "sampled_nonsample_variants": 92,
+ "top": [
+ [
+ "other:default title",
+ 16
+ ],
+ [
+ "other:x-small",
+ 3
+ ],
+ [
+ "other:x-large",
+ 3
+ ]
+ ]
+ },
+ "Designtex": {
+ "sampled_nonsample_variants": 40,
+ "top": [
+ [
+ "single_roll",
+ 75
+ ],
+ [
+ "roll_other",
+ 22
+ ],
+ [
+ "per_yard",
+ 2
+ ]
+ ]
+ },
+ "DW Bespoke Studio": {
+ "sampled_nonsample_variants": 156,
+ "top": [
+ [
+ "other:gold textured metallic /",
+ 40
+ ],
+ [
+ "other:peel and stick / 2ft x 1",
+ 20
+ ],
+ [
+ "other:prepasted / 2ft x 12ft",
+ 20
+ ]
+ ]
+ },
+ "Gaston Y Daniela": {
+ "sampled_nonsample_variants": 40,
+ "top": [
+ [
+ "per_yard",
+ 100
+ ]
+ ]
+ },
+ "GP & J Baker": {
+ "sampled_nonsample_variants": 40,
+ "top": [
+ [
+ "per_yard",
+ 100
+ ]
+ ]
+ },
+ "Graham & Brown": {
+ "sampled_nonsample_variants": 35,
+ "top": [
+ [
+ "other:default title",
+ 89
+ ],
+ [
+ "roll_other",
+ 11
+ ]
+ ]
+ },
+ "Innovations USA": {
+ "sampled_nonsample_variants": 52,
+ "top": [
+ [
+ "per_yard",
+ 73
+ ],
+ [
+ "single_roll",
+ 27
+ ]
+ ]
+ },
+ "Kravet": {
+ "sampled_nonsample_variants": 40,
+ "top": [
+ [
+ "per_yard",
+ 100
+ ]
+ ]
+ },
+ "Lee Jofa": {
+ "sampled_nonsample_variants": 40,
+ "top": [
+ [
+ "per_yard",
+ 100
+ ]
+ ]
+ },
+ "Malibu Wallpaper": {
+ "sampled_nonsample_variants": 10,
+ "top": [
+ [
+ "other:standard",
+ 90
+ ],
+ [
+ "single_roll",
+ 10
+ ]
+ ]
+ },
+ "Mulberry": {
+ "sampled_nonsample_variants": 29,
+ "top": [
+ [
+ "per_yard",
+ 100
+ ]
+ ]
+ },
+ "Newwall": {
+ "sampled_nonsample_variants": 5,
+ "top": [
+ [
+ "single_roll",
+ 100
+ ]
+ ]
+ },
+ "Phillipe Romano": {
+ "sampled_nonsample_variants": 9,
+ "top": [
+ [
+ "other:default title",
+ 100
+ ]
+ ]
+ },
+ "Pixels": {
+ "sampled_nonsample_variants": 240,
+ "top": [
+ [
+ "other:12\" x 16\" (96 pieces)",
+ 4
+ ],
+ [
+ "other:12\" x 16\" (252 pieces)",
+ 4
+ ],
+ [
+ "other:18\" x 24\" (500 pieces)",
+ 4
+ ]
+ ]
+ },
+ "Rebel Walls": {
+ "sampled_nonsample_variants": 42,
+ "top": [
+ [
+ "panel_mural",
+ 95
+ ],
+ [
+ "other:default title",
+ 5
+ ]
+ ]
+ },
+ "Romo": {
+ "sampled_nonsample_variants": 5,
+ "top": [
+ [
+ "single_roll",
+ 60
+ ],
+ [
+ "other:default title",
+ 20
+ ],
+ [
+ "roll_other",
+ 20
+ ]
+ ]
+ },
+ "Ronald Redding": {
+ "sampled_nonsample_variants": 40,
+ "top": [
+ [
+ "other:default title",
+ 100
+ ]
+ ]
+ },
+ "Sandberg": {
+ "sampled_nonsample_variants": 40,
+ "top": [
+ [
+ "other:sold per bolt (20.5in x ",
+ 100
+ ]
+ ]
+ },
+ "Schumacher": {
+ "sampled_nonsample_variants": 40,
+ "top": [
+ [
+ "roll_other",
+ 68
+ ],
+ [
+ "single_roll",
+ 32
+ ]
+ ]
+ },
+ "Thibaut": {
+ "sampled_nonsample_variants": 19,
+ "top": [
+ [
+ "single_roll",
+ 95
+ ],
+ [
+ "other:default title",
+ 5
+ ]
+ ]
+ },
+ "Tres Tintas": {
+ "sampled_nonsample_variants": 40,
+ "top": [
+ [
+ "per_meter",
+ 52
+ ],
+ [
+ "roll_other",
+ 48
+ ]
+ ]
+ },
+ "Versace": {
+ "sampled_nonsample_variants": 8,
+ "top": [
+ [
+ "single_roll",
+ 88
+ ],
+ [
+ "other:standard",
+ 12
+ ]
+ ]
+ },
+ "William Morris": {
+ "sampled_nonsample_variants": 40,
+ "top": [
+ [
+ "other:bolt 20.28in x 33ft",
+ 82
+ ],
+ [
+ "other:bolt 0in x 33ft",
+ 10
+ ],
+ [
+ "other:bolt",
+ 8
+ ]
+ ]
+ }
+}
\ No newline at end of file
← 9d3551fd Bucket B Harlequin: PG-first roll-variant recovery script (c
·
back to Designer Wallcoverings
·
Bucket B Mind the Gap: roll-variant recovery script (PG-firs 1e04847a →