[object Object]

← 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

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 →