← back to Designer Wallcoverings
fix the 2 DWTT wave-1 conflict SKUs (AT57882, T41013): PDP-confirmed single-roll dims + uom copy
4d58b2a4a0c78b39ec803f75beb9d4f4d34033d9 · 2026-07-02 17:45:00 -0700 · Steve
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Files touched
A shopify/scripts/audits/dwtt-conflict-sku-fix.jsonA shopify/scripts/fix-dwtt-conflict-skus.py
Diff
commit 4d58b2a4a0c78b39ec803f75beb9d4f4d34033d9
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jul 2 17:45:00 2026 -0700
fix the 2 DWTT wave-1 conflict SKUs (AT57882, T41013): PDP-confirmed single-roll dims + uom copy
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
shopify/scripts/audits/dwtt-conflict-sku-fix.json | 54 ++++++++++
shopify/scripts/fix-dwtt-conflict-skus.py | 123 ++++++++++++++++++++++
2 files changed, 177 insertions(+)
diff --git a/shopify/scripts/audits/dwtt-conflict-sku-fix.json b/shopify/scripts/audits/dwtt-conflict-sku-fix.json
new file mode 100644
index 00000000..fef8d4b6
--- /dev/null
+++ b/shopify/scripts/audits/dwtt-conflict-sku-fix.json
@@ -0,0 +1,54 @@
+{
+ "generated_at": "2026-07-02T17:44:49.794957",
+ "copy": "Priced per single roll. Single Roll Dimensions. Packed in 2 roll increments only.",
+ "planned": {
+ "AT57882": {
+ "before": {
+ "title": "Tate Trellis Green Wallcovering",
+ "handle": "tate-trellis-green-anna-french",
+ "status": "ACTIVE",
+ "width": "27\" x 4.5yds",
+ "length": "11 Yards",
+ "uom": "Full Roll"
+ },
+ "writes": {
+ "width": "27 in (68.58 cm)",
+ "length": "5.5 yd (5.03 m)",
+ "unit_of_measure": "Priced per single roll. Single Roll Dimensions. Packed in 2 roll increments only."
+ }
+ },
+ "T41013": {
+ "before": {
+ "title": "Hinton Tunnel Ivory | Thibaut",
+ "handle": "dwtt-80529-designer-wallcoverings-los-angeles",
+ "status": "ACTIVE",
+ "width": "27 Inches",
+ "length": "4.5 yd (13.5 ft)",
+ "uom": ""
+ },
+ "writes": {
+ "length": "4.5 yd (4.11 m)",
+ "unit_of_measure": "Priced per single roll. Single Roll Dimensions. Packed in 2 roll increments only."
+ }
+ }
+ },
+ "wrote": 5,
+ "after": {
+ "AT57882": {
+ "title": "Tate Trellis Green Wallcovering",
+ "handle": "tate-trellis-green-anna-french",
+ "status": "ACTIVE",
+ "width": "27 in (68.58 cm)",
+ "length": "5.5 yd (5.03 m)",
+ "uom": "Priced per single roll. Single Roll Dimensions. Packed in 2 roll increments only."
+ },
+ "T41013": {
+ "title": "Hinton Tunnel Ivory | Thibaut",
+ "handle": "dwtt-80529-designer-wallcoverings-los-angeles",
+ "status": "ACTIVE",
+ "width": "27 Inches",
+ "length": "4.5 yd (4.11 m)",
+ "uom": "Priced per single roll. Single Roll Dimensions. Packed in 2 roll increments only."
+ }
+ }
+}
\ No newline at end of file
diff --git a/shopify/scripts/fix-dwtt-conflict-skus.py b/shopify/scripts/fix-dwtt-conflict-skus.py
new file mode 100644
index 00000000..ee97d994
--- /dev/null
+++ b/shopify/scripts/fix-dwtt-conflict-skus.py
@@ -0,0 +1,123 @@
+"""
+WAVE-1 FOLLOW-UP: resolve the 2 exception SKUs from
+set-dwtt-single-roll-dimensions.py (Steve-approved 2026-07-02).
+
+ AT57882 (gid 7552689897523) — Shopify length "11 Yards" is a stale
+ double-roll value; PDP-confirmed single roll is 5.5 yd. Width included
+ in case the main run found it empty-but-unconfirmed.
+ T41013 (gid 7552706543667) — "4.5 yd (13.5 ft)" == "4.5 yd (4.11 m)"
+ physically; normalize to the catalog metric format so the uom copy
+ can land consistently with its siblings.
+
+Both also receive the single-roll uom COPY that was withheld pending a
+confirmed length.
+
+Run: SHOPIFY_ADMIN_TOKEN=... python3 fix-dwtt-conflict-skus.py [--dry-run]
+Cost: $0 (Shopify Admin API only).
+"""
+import json, os, sys, time, urllib.request, datetime
+
+TOKEN = os.environ["SHOPIFY_ADMIN_TOKEN"]
+SHOP = "designer-laboratory-sandbox.myshopify.com"
+GQL = f"https://{SHOP}/admin/api/2024-10/graphql.json"
+DRY = "--dry-run" in sys.argv or "--dry" in sys.argv
+COPY = "Priced per single roll. Single Roll Dimensions. Packed in 2 roll increments only."
+AUDIT_PATH = os.path.expanduser(
+ "~/Projects/Designer-Wallcoverings/shopify/scripts/audits/dwtt-conflict-sku-fix.json")
+
+FIXES = {
+ "gid://shopify/Product/7552689897523": dict(
+ mfr="AT57882", width="27 in (68.58 cm)", length="5.5 yd (5.03 m)"),
+ "gid://shopify/Product/7552706543667": dict(
+ mfr="T41013", width=None, length="4.5 yd (4.11 m)"),
+}
+
+def gql(q, v=None):
+ body = json.dumps({"query": q, "variables": v or {}}).encode()
+ req = urllib.request.Request(GQL, data=body, headers={
+ "X-Shopify-Access-Token": TOKEN, "Content-Type": "application/json"})
+ for _ in range(8):
+ try:
+ with urllib.request.urlopen(req) as r:
+ d = json.load(r)
+ if d.get("errors"):
+ if "THROTTLED" in json.dumps(d["errors"]):
+ time.sleep(2.5); continue
+ raise SystemExit("GQL ERR: " + json.dumps(d["errors"]))
+ return d["data"]
+ except urllib.error.HTTPError as e:
+ if e.code == 429:
+ time.sleep(2.5); continue
+ raise
+ raise SystemExit("retries exhausted")
+
+READ = """
+query($ids:[ID!]!){
+ nodes(ids:$ids){
+ ... on Product {
+ id title handle status
+ w:metafield(namespace:"global",key:"width"){value}
+ l:metafield(namespace:"global",key:"length"){value}
+ uom:metafield(namespace:"global",key:"unit_of_measure"){value}
+ }
+ }
+}
+"""
+
+SET = """
+mutation($mfs:[MetafieldsSetInput!]!){
+ metafieldsSet(metafields:$mfs){
+ metafields{ id }
+ userErrors{ field message }
+ }
+}
+"""
+
+def snapshot():
+ nodes = gql(READ, {"ids": list(FIXES.keys())})["nodes"]
+ return {n["id"]: dict(title=n["title"], handle=n["handle"], status=n["status"],
+ width=(n["w"] or {}).get("value", ""),
+ length=(n["l"] or {}).get("value", ""),
+ uom=(n["uom"] or {}).get("value", ""))
+ for n in nodes if n}
+
+def main():
+ before = snapshot()
+ writes, planned = [], {}
+ for gid, fix in FIXES.items():
+ cur = before[gid]
+ queued = {}
+ if fix["width"] and cur["width"] != fix["width"]:
+ queued["width"] = fix["width"]
+ if cur["length"] != fix["length"]:
+ queued["length"] = fix["length"]
+ if cur["uom"] != COPY:
+ queued["unit_of_measure"] = COPY
+ planned[fix["mfr"]] = dict(before=cur, writes=queued)
+ for key, val in queued.items():
+ writes.append({"ownerId": gid, "namespace": "global", "key": key,
+ "type": "single_line_text_field", "value": val})
+
+ print(json.dumps(planned, indent=2))
+ if DRY:
+ print(f"DRY RUN — {len(writes)} metafields would be set."); return
+ if writes:
+ d = gql(SET, {"mfs": writes})
+ errs = d["metafieldsSet"]["userErrors"]
+ if errs:
+ raise SystemExit("userErrors: " + json.dumps(errs))
+ after = snapshot()
+ audit = dict(generated_at=datetime.datetime.now().isoformat(), copy=COPY,
+ planned=planned, wrote=len(writes),
+ after={FIXES[g]["mfr"]: a for g, a in after.items()})
+ with open(AUDIT_PATH, "w") as f:
+ json.dump(audit, f, indent=2)
+ print(f"DONE. wrote {len(writes)} metafields. Audit -> {AUDIT_PATH}")
+ for g, a in after.items():
+ ok = (a["length"] == FIXES[g]["length"] and a["uom"] == COPY and
+ (not FIXES[g]["width"] or a["width"] == FIXES[g]["width"]))
+ print(f" {FIXES[g]['mfr']}: {'VERIFIED' if ok else 'MISMATCH'} "
+ f"w={a['width']!r} l={a['length']!r} uom-ok={a['uom']==COPY}")
+
+if __name__ == "__main__":
+ main()
← 9bd50424 discontinued-sweep: fix pgrep self-match deadlock in chained
·
back to Designer Wallcoverings
·
auto-save: 2026-07-02T17:51:29 (6 files) — pending-approval/ 2f24a2ee →