[object Object]

← back to Designerwallcoverings

TK-11414: reversible product-weight backfill script (samples 0.25lb, sellable per-type defaults)

156c7c2bb04e97836f09e5c8a374d8a5891d38f5 · 2026-09-10 13:32:03 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 156c7c2bb04e97836f09e5c8a374d8a5891d38f5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 10 13:32:03 2026 -0700

    TK-11414: reversible product-weight backfill script (samples 0.25lb, sellable per-type defaults)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 scripts/weight-backfill-tk11414/.gitignore   |  6 +++
 scripts/weight-backfill-tk11414/README.md    |  7 +++
 scripts/weight-backfill-tk11414/build.py     | 68 ++++++++++++++++++++++++++++
 scripts/weight-backfill-tk11414/poll-done.sh | 18 ++++++++
 4 files changed, 99 insertions(+)

diff --git a/scripts/weight-backfill-tk11414/.gitignore b/scripts/weight-backfill-tk11414/.gitignore
new file mode 100644
index 0000000..9dd6dd5
--- /dev/null
+++ b/scripts/weight-backfill-tk11414/.gitignore
@@ -0,0 +1,6 @@
+*.jsonl
+*.json
+*.tsv
+*.copy
+*.log
+*.txt
diff --git a/scripts/weight-backfill-tk11414/README.md b/scripts/weight-backfill-tk11414/README.md
new file mode 100644
index 0000000..ea627f8
--- /dev/null
+++ b/scripts/weight-backfill-tk11414/README.md
@@ -0,0 +1,7 @@
+# TK-11414 weight backfill (2026-09-10, APPROVED by Steve)
+Backfills product weight on active zero-weight Shopify variants (samples 0.25 lb,
+sellable per-product-type flat defaults). Reversible via dw_unified.shopify_weight_backfill_tk11414.
+- build.py  : parse combined export -> restore-map.tsv + mutations.jsonl (dry-run counts)
+- poll-done.sh : poll the bulk mutation to completion, download result
+Mechanism: GraphQL bulkOperationRunMutation(inventoryItemUpdate) via staged upload, FULL token.
+Data files (exports, mutations, restore map) are gitignored (bulk).
diff --git a/scripts/weight-backfill-tk11414/build.py b/scripts/weight-backfill-tk11414/build.py
new file mode 100644
index 0000000..c440b89
--- /dev/null
+++ b/scripts/weight-backfill-tk11414/build.py
@@ -0,0 +1,68 @@
+import json, math, sys
+SRC="/tmp/tk11414/combined.jsonl"
+LB=453.59237
+SAMPLE_LB=0.25
+TYPE_DEFAULT={  # product_type -> sellable default lb
+ "Wallcovering":3.0,"Wallcoverings":3.0,"Wallpaper":3.0,"Metallic Wallcovering":3.0,
+ "Commercial Wallcovering":3.0,"Mural":4.0,"Fabric":1.0,"Commercial Fabric":1.0,
+ "Commercial Drapery":1.0,"Trim":0.5,"Acoustic Panel":6.0,"Pillow":1.5,
+ "Upholstered Walls/Panels":6.0,"Tin Ceiling Tile":2.0,"Hardware":1.0,"Furniture":15.0,
+ "Memo Sample":0.25,
+}
+FALLBACK=2.0
+def is_sample(sku,title,price):
+    s=(sku or '').lower()
+    if s.endswith('-sample') or 'sample' in s: return True
+    t=(title or '').lower()
+    if 'sample' in t or 'memo' in t: return True
+    try:
+        if price is not None and abs(float(price)-4.25)<0.01: return True
+    except: pass
+    return False
+def zero(v): return (v is None) or (float(v)==0.0)
+
+prod={}
+with open(SRC) as f:
+    for l in f:
+        try:o=json.loads(l)
+        except:continue
+        if '/Product/' in o.get('id',''):
+            prod[o['id']]=(o.get('status'),o.get('vendor') or 'NULL',o.get('productType') or '')
+
+from collections import Counter,defaultdict
+by_class=Counter(); by_type=Counter(); by_ven=Counter()
+restore=open('restore-map.tsv','w')   # iid \t sku \t ptype \t prior_lb \t new_lb
+muts=open('mutations.jsonl','w')
+n=0; skipped_no_iid=0
+with open(SRC) as f:
+    for l in f:
+        try:o=json.loads(l)
+        except:continue
+        if '/ProductVariant/' not in o.get('id','') and '__parentId' not in o: continue
+        if '__parentId' not in o: continue
+        st,ven,ptype=prod.get(o['__parentId'],('?','?',''))
+        if st!='ACTIVE': continue
+        ii=(o.get('inventoryItem') or {})
+        iid=ii.get('id'); wt=(ii.get('measurement') or {}).get('weight')
+        prior = None if wt is None else wt.get('value')
+        if not zero(prior): continue
+        if not iid: skipped_no_iid+=1; continue
+        sku=o.get('sku') or ''; title=o.get('title'); price=o.get('price')
+        if is_sample(sku,title,price):
+            new=SAMPLE_LB; cls='sample'
+        else:
+            new=TYPE_DEFAULT.get(ptype,FALLBACK); cls='sellable:'+(ptype or 'BLANK')
+        by_class['sample' if cls=='sample' else 'sellable']+=1
+        by_type[ptype or 'BLANK']+=1 if cls!='sample' else 0
+        by_ven[ven]+=1
+        pl = 0.0 if prior is None else float(prior)
+        restore.write("%s\t%s\t%s\t%.4f\t%.4f\n"%(iid,sku,ptype,pl,new))
+        muts.write(json.dumps({"id":iid,"input":{"measurement":{"weight":{"value":new,"unit":"POUNDS"}}}})+"\n")
+        n+=1
+restore.close(); muts.close()
+print("TARGET active zero-weight variants:",n," (skipped no-iid:",skipped_no_iid,")")
+print("by class:",dict(by_class))
+print("sellable by product_type default:")
+for t,c in by_type.most_common():
+    if c: print("   %-28s %6d -> %.2f lb"%(t,c,TYPE_DEFAULT.get(t,FALLBACK)))
+print("top 8 vendors in target:", by_ven.most_common(8))
diff --git a/scripts/weight-backfill-tk11414/poll-done.sh b/scripts/weight-backfill-tk11414/poll-done.sh
new file mode 100755
index 0000000..9daa1dc
--- /dev/null
+++ b/scripts/weight-backfill-tk11414/poll-done.sh
@@ -0,0 +1,18 @@
+#!/bin/zsh
+cd ~/Projects/designerwallcoverings/scripts/weight-backfill-tk11414
+FULL=$(grep -E '^SHOPIFY_FULL_ACCESS_TOKEN=' ~/Projects/secrets-manager/.env | head -1 | cut -d= -f2- | tr -d '"'"'"' \r')
+STORE="designer-laboratory-sandbox.myshopify.com"
+Q='{"query":"{ currentBulkOperation(type: MUTATION) { id status objectCount url partialDataUrl errorCode } }"}'
+while true; do
+  RESP=$(curl -s -X POST "https://$STORE/admin/api/2024-10/graphql.json" -H "X-Shopify-Access-Token: $FULL" -H "Content-Type: application/json" -d "$Q")
+  ST=$(echo "$RESP" | python3 -c 'import json,sys;print(json.load(sys.stdin)["data"]["currentBulkOperation"]["status"])')
+  echo "$(date +%H:%M:%S) $ST $(echo "$RESP" | python3 -c 'import json,sys;print(json.load(sys.stdin)["data"]["currentBulkOperation"]["objectCount"])')" >> poll.log
+  if [ "$ST" = "COMPLETED" ] || [ "$ST" = "FAILED" ]; then
+    echo "$RESP" > mut-final.json
+    URL=$(echo "$RESP" | python3 -c 'import json,sys;d=json.load(sys.stdin)["data"]["currentBulkOperation"];print(d.get("url") or "")')
+    [ -n "$URL" ] && curl -s "$URL" -o mut-result.jsonl
+    break
+  fi
+  sleep 25
+done
+echo "DONE $(date +%H:%M:%S)" >> poll.log

← 6cea993 auto-data-snapshot: 2026-09-10T13:02:09 (1 data files) — scr  ·  back to Designerwallcoverings  ·  auto-data-snapshot: 2026-09-10T13:39:50 (2 data files) — scr e76ef3e →