[object Object]

← back to Designer Wallcoverings

TK-11534: doubleroll length backfill — additive --only-skus/--length-map/--width-map/--catalog-only flags + 47-SKU recoverable backfill assets (Steve-approved fix-forward)

31eff9bd88985de7a74fb41e014634a06e95b3b8 · 2026-09-13 00:23:57 -0700 · Steve

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUKRS8nTAFtE1hmVpgSHm2

Files touched

Diff

commit 31eff9bd88985de7a74fb41e014634a06e95b3b8
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Sep 13 00:23:57 2026 -0700

    TK-11534: doubleroll length backfill — additive --only-skus/--length-map/--width-map/--catalog-only flags + 47-SKU recoverable backfill assets (Steve-approved fix-forward)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01VUKRS8nTAFtE1hmVpgSHm2
---
 shopify/scripts/set-doubleroll-lengths.py          |  57 +++-
 .../scripts/tk11534-doubleroll/cores-thibaut.txt   |  91 +++++
 .../scripts/tk11534-doubleroll/cores-wallquest.txt | 117 +++++++
 .../tk11534-doubleroll/dr-before-snapshot.json     | 375 +++++++++++++++++++++
 .../tk11534-doubleroll/dr-snap-rollback.mjs        |  43 +++
 .../scripts/tk11534-doubleroll/lenmap-thibaut.txt  |  33 ++
 .../tk11534-doubleroll/lenmap-wallquest.txt        |  25 ++
 .../tk11534-doubleroll/widthmap-thibaut.txt        |  33 ++
 .../tk11534-doubleroll/widthmap-wallquest.txt      |  25 ++
 9 files changed, 796 insertions(+), 3 deletions(-)

diff --git a/shopify/scripts/set-doubleroll-lengths.py b/shopify/scripts/set-doubleroll-lengths.py
index 35f4bf7c..cfb2edbe 100644
--- a/shopify/scripts/set-doubleroll-lengths.py
+++ b/shopify/scripts/set-doubleroll-lengths.py
@@ -61,6 +61,45 @@ CFG = VENDORS[VKEY]
 BEFORE = os.path.join(HERE, "audits", f"doubleroll-lengths-{VKEY}-before.jsonl")
 os.makedirs(os.path.dirname(BEFORE), exist_ok=True)
 
+# TK-11534 (additive, opt-in — default behavior unchanged when flags absent):
+#   --only-skus <file>  scope processing to a newline-delimited allowlist of dw_sku CORES
+#                       (prefix+number, e.g. DWTT-80002 / DWQW-57999); products outside it
+#                       are fully skipped (no writes at all).
+#   --catalog-only      resolve LENGTH strictly from the vendor STAGING catalog — do NOT
+#                       derive from the live global.length, the width field, or the
+#                       width->length standard map. This stamps ONLY the staging-recoverable
+#                       subset and never guesses a length (honors "don't derive the ~120").
+CATALOG_ONLY = "--catalog-only" in sys.argv
+_only_path = arg("--only-skus")
+def core_of(sku):
+    m = re.match(r"^(DW[A-Z]{2}|LOD|CDA)-\d+", (sku or "").upper())
+    return m.group(0) if m else (sku or "").upper()
+ONLY_CORES = None
+if _only_path:
+    with open(_only_path) as _f:
+        ONLY_CORES = {core_of(l.strip()) for l in _f if l.strip()}
+#   --length-map <file>  TSV "CORE\tlength-string" supplying the AUTHORITATIVE staging length
+#                        per product (resolved via the correct per-vendor mfr_sku join, which
+#                        the naive dwsku catalog join misses). Highest-priority length source;
+#                        the script's OWN single/double classifier + writer still apply.
+def _load_map(path):
+    m = {}
+    if path:
+        with open(path) as _f:
+            for _l in _f:
+                _l = _l.rstrip("\n")
+                if not _l.strip():
+                    continue
+                _c, _, _v = _l.partition("\t")
+                if _c.strip() and _v.strip():
+                    m[core_of(_c.strip())] = _v.strip()
+    return m
+LEN_MAP = _load_map(arg("--length-map"))
+#   --width-map <file>   TSV "CORE\twidth-string" supplying the staging WIDTH (mfr_sku join),
+#                        used only by the single/double classifier's plausible-roll-width gate
+#                        when the live global.width metafield is absent. Never derives length.
+WIDTH_MAP = _load_map(arg("--width-map"))
+
 # single ~28-32 sqft, double ~56-64; windows chosen wide enough for real variance,
 # narrow enough that a mural (14 sqft) or a jumbo panel falls in the ODD gap.
 SINGLE_LO, SINGLE_HI = 22, 42
@@ -231,6 +270,9 @@ def main():
                              "roll_variants": [v["sku"] for v in roll_vars]}) + "\n")
         if not roll_vars:
             continue
+        # TK-11534 scope allowlist: skip products outside the flagged set entirely (no writes)
+        if ONLY_CORES is not None and core_of(dwsku_of(roll_vars[0]["sku"])) not in ONLY_CORES:
+            continue
         roll += 1
 
         # ---- min/step = 2 (idempotent), always for roll products ----
@@ -246,19 +288,28 @@ def main():
         dwsku = dwsku_of(roll_vars[0]["sku"])
         cat_w, cat_L = catalog.get(dwsku, ("", ""))
         W = cat_w or (p["w"] or {}).get("value") or ""
-        L = cat_L or (p["len"] or {}).get("value") or ""
+        _mapW = WIDTH_MAP.get(core_of(dwsku))
+        if not width_in(W) and _mapW:
+            W = _mapW
+        # --catalog-only: LENGTH strictly from staging catalog (no live global.length fallback)
+        L = cat_L if CATALOG_ONLY else (cat_L or (p["len"] or {}).get("value") or "")
+        # --length-map override: authoritative staging length resolved via correct mfr_sku join
+        _mapL = LEN_MAP.get(core_of(dwsku))
+        if _mapL:
+            L = _mapL
         # Dirty WallQuest data: the length may be empty while the full "W x L" dimension is
         # crammed into the width field ("20.5in x 16.5ft"), OR a "W x L" string sits in the
         # length field itself (CDA rows). Reduce any "A x B" to just its LENGTH token (after
         # the last 'x') so we never treat a width×length string as a bare length (which would
         # double BOTH numbers). Then fall back to the width field's 2nd token if length is empty.
         L = only_length(L)
-        if not length_ft(L):
+        if not length_ft(L) and not CATALOG_ONLY:
             L = only_length(W, second_only=True)
         wi = width_in(W)
         # last resort ($0, no re-scrape): fill from the vendor's own width->length standard,
         # keyed on the (now-cleaned) width. This value is the vendor's standard roll length.
-        if not length_ft(L) and wi is not None:
+        # Skipped under --catalog-only (never derive a length; leave such products ODD/untouched).
+        if not length_ft(L) and wi is not None and not CATALOG_ONLY:
             L = width_std.get(round(wi*2)/2, L)
         lf = length_ft(L)
         if not (wi and lf and lf > 0):
diff --git a/shopify/scripts/tk11534-doubleroll/cores-thibaut.txt b/shopify/scripts/tk11534-doubleroll/cores-thibaut.txt
new file mode 100644
index 00000000..231d4546
--- /dev/null
+++ b/shopify/scripts/tk11534-doubleroll/cores-thibaut.txt
@@ -0,0 +1,91 @@
+DWTT-70856
+DWTT-70844
+DWTT-70668
+DWTT-70660
+DWTT-70169
+DWTT-80002
+DWTT-80004
+DWTT-80005
+DWTT-80006
+DWTT-74478
+DWTT-74563
+DWTT-74565
+DWTT-80307
+DWTT-70630
+DWTT-70570
+DWTT-70070
+DWTT-70360
+DWTT-70380
+DWTT-71046
+DWTT-71047
+DWTT-71049
+DWTT-71078
+DWTT-71079
+DWTT-71081
+DWTT-71083
+DWTT-71053
+DWTT-71054
+DWTT-71055
+DWTT-71092
+DWTT-71097
+DWTT-71098
+DWTT-71099
+DWTT-71028
+DWTT-71030
+DWTT-80947
+DWTT-80957
+DWTT-80964
+DWTT-80965
+DWTT-80980
+DWTT-80883
+DWTT-80886
+DWTT-80908
+DWTT-80912
+DWTT-80934
+DWTT-80935
+DWTT-80752
+DWTT-80755
+DWTT-80756
+DWTT-70251
+DWTT-80758
+DWTT-80759
+DWTT-80760
+DWTT-80769
+DWTT-80776
+DWTT-80780
+DWTT-80652
+DWTT-80657
+DWTT-70581
+DWTT-70582
+DWTT-80681
+DWTT-80695
+DWTT-70554
+DWTT-81110
+DWTT-80706
+DWTT-80716
+DWTT-71128
+DWTT-80156
+DWTT-81061
+DWTT-81074
+DWTT-81075
+DWTT-81077
+DWTT-80241
+DWTT-80244
+DWTT-80246
+DWTT-80255
+DWTT-80267
+DWTT-80274
+DWTT-80617
+DWTT-80620
+DWTT-80627
+DWTT-80789
+DWTT-80790
+DWTT-80791
+DWTT-80041
+DWTT-80042
+DWTT-80569
+DWTT-80570
+DWTT-80571
+DWTT-80238
+DWTT-80239
+DWTT-80240
diff --git a/shopify/scripts/tk11534-doubleroll/cores-wallquest.txt b/shopify/scripts/tk11534-doubleroll/cores-wallquest.txt
new file mode 100644
index 00000000..178caa31
--- /dev/null
+++ b/shopify/scripts/tk11534-doubleroll/cores-wallquest.txt
@@ -0,0 +1,117 @@
+DWQW-57001
+DWQW-57002
+DWQW-58287
+DWQW-58289
+DWQW-58603
+DWQW-57999
+DWQW-58456
+DWQW-58483
+DWQW-56166
+DWQW-57522
+DWQW-57523
+DWQW-57525
+DWQW-57526
+DWQW-57245
+DWQW-57247
+DWQW-57248
+DWQW-58416
+DWQW-56976
+DWQW-56977
+DWQW-56978
+DWQW-56980
+DWQW-56981
+LOD-43303
+LOD-43304
+LOD-43305
+LOD-43306
+LOD-43307
+LOD-43308
+LOD-43309
+LOD-43310
+LOD-43318
+LOD-43322
+LOD-43323
+LOD-43324
+LOD-43329
+LOD-43331
+LOD-43332
+LOD-43333
+LOD-43335
+CDA-96700
+CDA-96701
+CDA-96702
+CDA-96703
+CDA-96704
+CDA-96705
+CDA-96706
+CDA-96707
+CDA-96708
+CDA-96709
+CDA-96714
+CDA-96715
+CDA-96716
+CDA-96717
+CDA-96719
+CDA-96720
+CDA-96721
+CDA-96722
+CDA-96723
+CDA-96724
+CDA-96725
+CDA-96726
+CDA-96727
+CDA-96729
+CDA-96730
+CDA-96731
+CDA-96732
+CDA-96733
+CDA-96734
+CDA-96735
+CDA-96736
+CDA-96737
+CDA-96738
+CDA-96739
+CDA-96740
+CDA-96741
+CDA-96742
+CDA-96743
+CDA-96744
+CDA-96745
+CDA-96746
+CDA-96747
+CDA-96748
+CDA-96749
+CDA-96750
+CDA-96751
+CDA-96753
+CDA-96754
+CDA-96755
+CDA-96756
+CDA-96757
+CDA-96758
+CDA-96759
+CDA-96760
+DWQW-58182
+DWQW-57544
+DWQW-57502
+DWQW-57503
+DWQW-57504
+DWQW-57506
+DWQW-57217
+DWQW-57218
+DWQW-57219
+DWQW-57224
+DWQW-57225
+DWQW-57099
+DWQW-57100
+DWQW-57101
+DWQW-57103
+DWQW-57125
+DWQW-57126
+DWQW-57127
+DWQW-57128
+DWQW-57133
+DWQW-57134
+DWQW-57135
+DWQW-56973
+DWQW-56974
diff --git a/shopify/scripts/tk11534-doubleroll/dr-before-snapshot.json b/shopify/scripts/tk11534-doubleroll/dr-before-snapshot.json
new file mode 100644
index 00000000..315667b4
--- /dev/null
+++ b/shopify/scripts/tk11534-doubleroll/dr-before-snapshot.json
@@ -0,0 +1,375 @@
+[
+ {
+  "core": "DWTT-70169",
+  "gid": "gid://shopify/Product/6944670613555",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-80002",
+  "gid": "gid://shopify/Product/7552655917107",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-80004",
+  "gid": "gid://shopify/Product/7552656015411",
+  "sr": null,
+  "dr": null,
+  "uom": "Priced Per Single Roll"
+ },
+ {
+  "core": "DWTT-80005",
+  "gid": "gid://shopify/Product/7552656080947",
+  "sr": null,
+  "dr": null,
+  "uom": "Priced Per Single Roll"
+ },
+ {
+  "core": "DWTT-80006",
+  "gid": "gid://shopify/Product/7552656146483",
+  "sr": null,
+  "dr": null,
+  "uom": "Priced Per Single Roll"
+ },
+ {
+  "core": "DWTT-71046",
+  "found": false
+ },
+ {
+  "core": "DWTT-71047",
+  "found": false
+ },
+ {
+  "core": "DWTT-71049",
+  "found": false
+ },
+ {
+  "core": "DWTT-71078",
+  "found": false
+ },
+ {
+  "core": "DWTT-71079",
+  "found": false
+ },
+ {
+  "core": "DWTT-71081",
+  "found": false
+ },
+ {
+  "core": "DWTT-71083",
+  "found": false
+ },
+ {
+  "core": "DWTT-71092",
+  "found": false
+ },
+ {
+  "core": "DWTT-71097",
+  "found": false
+ },
+ {
+  "core": "DWTT-71098",
+  "found": false
+ },
+ {
+  "core": "DWTT-71099",
+  "found": false
+ },
+ {
+  "core": "DWTT-80752",
+  "gid": "gid://shopify/Product/7552722141235",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-80755",
+  "gid": "gid://shopify/Product/7552722305075",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-80756",
+  "gid": "gid://shopify/Product/7552722370611",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-70251",
+  "gid": "gid://shopify/Product/6944673464371",
+  "sr": null,
+  "dr": null,
+  "uom": "Priced Per Single Roll"
+ },
+ {
+  "core": "DWTT-80758",
+  "gid": "gid://shopify/Product/7552722468915",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-80759",
+  "gid": "gid://shopify/Product/7552722501683",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-80760",
+  "gid": "gid://shopify/Product/7552722534451",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-80769",
+  "gid": "gid://shopify/Product/7552722927667",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-80776",
+  "gid": "gid://shopify/Product/7552723288115",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-80780",
+  "gid": "gid://shopify/Product/7552723484723",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-80657",
+  "gid": "gid://shopify/Product/7552714604595",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-70581",
+  "gid": "gid://shopify/Product/6944685654067",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-70582",
+  "gid": "gid://shopify/Product/6944685686835",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-80681",
+  "gid": "gid://shopify/Product/7552715849779",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-70554",
+  "gid": "gid://shopify/Product/6944684769331",
+  "sr": null,
+  "dr": null,
+  "uom": "Priced Per Single Roll"
+ },
+ {
+  "core": "DWTT-80706",
+  "gid": "gid://shopify/Product/7552717848627",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWTT-80716",
+  "gid": "gid://shopify/Product/7552720306227",
+  "sr": null,
+  "dr": null,
+  "uom": null
+ },
+ {
+  "core": "DWQW-57999",
+  "gid": "gid://shopify/Product/7317649227827",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57522",
+  "gid": "gid://shopify/Product/7292575514675",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57523",
+  "gid": "gid://shopify/Product/7292576956467",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57525",
+  "gid": "gid://shopify/Product/7292578922547",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57526",
+  "gid": "gid://shopify/Product/7292579971123",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57245",
+  "gid": "gid://shopify/Product/7292377563187",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57247",
+  "gid": "gid://shopify/Product/7292379070515",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57248",
+  "gid": "gid://shopify/Product/7292379889715",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57502",
+  "gid": "gid://shopify/Product/7292554706995",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57503",
+  "gid": "gid://shopify/Product/7292555853875",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57504",
+  "gid": "gid://shopify/Product/7292556738611",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57506",
+  "gid": "gid://shopify/Product/7292558770227",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57217",
+  "gid": "gid://shopify/Product/7292354461747",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57218",
+  "gid": "gid://shopify/Product/7292355280947",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57219",
+  "gid": "gid://shopify/Product/7292356067379",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57224",
+  "gid": "gid://shopify/Product/7292360654899",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57225",
+  "gid": "gid://shopify/Product/7292361539635",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57099",
+  "gid": "gid://shopify/Product/7292291088435",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57100",
+  "gid": "gid://shopify/Product/7292292038707",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57101",
+  "gid": "gid://shopify/Product/7292292857907",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57103",
+  "gid": "gid://shopify/Product/7292294365235",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57125",
+  "gid": "gid://shopify/Product/7292312059955",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57126",
+  "gid": "gid://shopify/Product/7292312846387",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57127",
+  "gid": "gid://shopify/Product/7292313927731",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ },
+ {
+  "core": "DWQW-57128",
+  "gid": "gid://shopify/Product/7292315009075",
+  "sr": null,
+  "dr": null,
+  "uom": "Full Roll"
+ }
+]
\ No newline at end of file
diff --git a/shopify/scripts/tk11534-doubleroll/dr-snap-rollback.mjs b/shopify/scripts/tk11534-doubleroll/dr-snap-rollback.mjs
new file mode 100644
index 00000000..9c6d5777
--- /dev/null
+++ b/shopify/scripts/tk11534-doubleroll/dr-snap-rollback.mjs
@@ -0,0 +1,43 @@
+import fs from 'node:fs';
+import { gql } from '/Users/macstudio3/Projects/designerwallcoverings/scripts/lib/shopify.mjs';
+const SC='/private/tmp/claude-501/-Users-macstudio3-Projects-designerwallcoverings/f5511869-92b7-4c17-a6d8-32c84b0a7711/scratchpad';
+const SNAP=SC+'/dr-before-snapshot.json';
+const MODE=process.argv[2]; // snap | undo
+const cores=[];
+for(const v of ['thibaut','wallquest']){ for(const l of fs.readFileSync(`${SC}/lenmap-${v}.txt`,'utf8').trim().split('\n')){ if(l) cores.push({vendor:v, core:l.split('\t')[0]}); } }
+async function findProduct(core){
+  // find the product owning a variant whose sku core matches
+  const d=await gql(`query($q:String!){ productVariants(first:5, query:$q){ nodes{ sku product{ id
+    sr:metafield(namespace:"global",key:"single_roll_length"){id value}
+    dr:metafield(namespace:"global",key:"double_roll_length"){id value}
+    uom:metafield(namespace:"global",key:"unit_of_measure"){id value} } } } }`, { q:`sku:${core}*` });
+  for(const v of d.productVariants.nodes){ const c=(v.sku||'').toUpperCase().match(/^(DW[A-Z]{2}|LOD|CDA)-\d+/); if(c&&c[0]===core) return v.product; }
+  return null;
+}
+if(MODE==='snap'){
+  const snap=[];
+  for(const {core} of cores){ const p=await findProduct(core); if(!p){snap.push({core,found:false});continue;}
+    snap.push({core, gid:p.id, sr:p.sr?.value??null, dr:p.dr?.value??null, uom:p.uom?.value??null}); }
+  fs.writeFileSync(SNAP, JSON.stringify(snap,null,1));
+  const withVals=snap.filter(s=>s.found!==false && (s.sr||s.dr));
+  console.log(`snapshot ${snap.length} cores -> ${SNAP}; already-nonnull SR/DR: ${withVals.length}`);
+} else if(MODE==='undo'){
+  const snap=JSON.parse(fs.readFileSync(SNAP,'utf8'));
+  const DEL=`mutation($mf:[MetafieldIdentifierInput!]!){ metafieldsDelete(metafields:$mf){ deletedMetafields{key} userErrors{message} } }`;
+  const SET=`mutation($mfs:[MetafieldsSetInput!]!){ metafieldsSet(metafields:$mfs){ userErrors{message} } }`;
+  let done=0;
+  for(const s of snap){ if(!s.gid) continue;
+    // restore each of sr/dr/uom to prior: delete if prior null else set
+    const toDel=[], toSet=[];
+    for(const k of [['single_roll_length','sr'],['double_roll_length','dr'],['unit_of_measure','uom']]){
+      if(s[k[1]]==null) toDel.push({ownerId:s.gid, namespace:'global', key:k[0]});
+      else toSet.push({ownerId:s.gid, namespace:'global', key:k[0], type:'single_line_text_field', value:s[k[1]]});
+    }
+    if(process.argv[3]==='--apply'){
+      if(toDel.length){ const r=await gql(DEL,{mf:toDel}); if(r?.__err) console.log('del err',s.core,JSON.stringify(r.__err).slice(0,120)); }
+      if(toSet.length){ const r=await gql(SET,{mfs:toSet}); if(r?.__err) console.log('set err',s.core,JSON.stringify(r.__err).slice(0,120)); }
+    }
+    done++;
+  }
+  console.log(`undo ${process.argv[3]==='--apply'?'APPLIED':'DRY'} on ${done} products`);
+}
diff --git a/shopify/scripts/tk11534-doubleroll/lenmap-thibaut.txt b/shopify/scripts/tk11534-doubleroll/lenmap-thibaut.txt
new file mode 100644
index 00000000..55c66ce2
--- /dev/null
+++ b/shopify/scripts/tk11534-doubleroll/lenmap-thibaut.txt
@@ -0,0 +1,33 @@
+DWTT-70169	4 yd (3.66 m)
+DWTT-80002	4.5 yd (4.11 m)
+DWTT-80004	4.5 yd (4.11 m)
+DWTT-80005	4.5 yd (4.11 m)
+DWTT-80006	4.5 yd (4.11 m)
+DWTT-71046	4.5 yd (4.11 m)
+DWTT-71047	4.5 yd (4.11 m)
+DWTT-71049	4.5 yd (4.11 m)
+DWTT-71078	4.5 yd (4.11 m)
+DWTT-71079	4.5 yd (4.11 m)
+DWTT-71081	4.5 yd (4.11 m)
+DWTT-71083	4.5 yd (4.11 m)
+DWTT-71092	4.5 yd (4.11 m)
+DWTT-71097	4.5 yd (4.11 m)
+DWTT-71098	4.5 yd (4.11 m)
+DWTT-71099	4.5 yd (4.11 m)
+DWTT-80752	4.5 yd (4.11 m)
+DWTT-80755	4.5 yd (4.11 m)
+DWTT-80756	4.5 yd (4.11 m)
+DWTT-70251	4.5 yd (4.11 m)
+DWTT-80758	4.5 yd (4.11 m)
+DWTT-80759	4.5 yd (4.11 m)
+DWTT-80760	4.5 yd (4.11 m)
+DWTT-80769	4.5 yd (4.11 m)
+DWTT-80776	4.5 yd (4.11 m)
+DWTT-80780	4.5 yd (4.11 m)
+DWTT-80657	4.5 yd (4.11 m)
+DWTT-70581	4.5 yd (4.11 m)
+DWTT-70582	4.5 yd (4.11 m)
+DWTT-80681	4.5 yd (4.11 m)
+DWTT-70554	4.5 yd (4.11 m)
+DWTT-80706	4.5 yd (4.11 m)
+DWTT-80716	4.5 yd (4.11 m)
diff --git a/shopify/scripts/tk11534-doubleroll/lenmap-wallquest.txt b/shopify/scripts/tk11534-doubleroll/lenmap-wallquest.txt
new file mode 100644
index 00000000..7aae97e4
--- /dev/null
+++ b/shopify/scripts/tk11534-doubleroll/lenmap-wallquest.txt
@@ -0,0 +1,25 @@
+DWQW-57999	9 yd | 8.2 m
+DWQW-57522	11 yd | 10 m
+DWQW-57523	11 yd | 10 m
+DWQW-57525	11 yd | 10 m
+DWQW-57526	11 yd | 10 m
+DWQW-57245	11 yd | 10 m
+DWQW-57247	11 yd | 10 m
+DWQW-57248	11 yd | 10 m
+DWQW-57502	11 yd | 10 m
+DWQW-57503	11 yd | 10 m
+DWQW-57504	11 yd | 10 m
+DWQW-57506	11 yd | 10 m
+DWQW-57217	9 yd | 8.2 m
+DWQW-57218	9 yd | 8.2 m
+DWQW-57219	9 yd | 8.2 m
+DWQW-57224	11 yd | 10 m
+DWQW-57225	11 yd | 10 m
+DWQW-57099	9 yd | 8.2 m
+DWQW-57100	9 yd | 8.2 m
+DWQW-57101	9 yd | 8.2 m
+DWQW-57103	9 yd | 8.2 m
+DWQW-57125	9 yd | 8.2 m
+DWQW-57126	9 yd | 8.2 m
+DWQW-57127	9 yd | 8.2 m
+DWQW-57128	9 yd | 8.2 m
diff --git a/shopify/scripts/tk11534-doubleroll/widthmap-thibaut.txt b/shopify/scripts/tk11534-doubleroll/widthmap-thibaut.txt
new file mode 100644
index 00000000..761e976e
--- /dev/null
+++ b/shopify/scripts/tk11534-doubleroll/widthmap-thibaut.txt
@@ -0,0 +1,33 @@
+DWTT-70169	36
+DWTT-80002	27 in  (68.58 cm)
+DWTT-80004	27" x 4.5yds
+DWTT-80005	27" x 4.5yds
+DWTT-80006	27" x 4.5yds
+DWTT-71046	54 in  (137.16 cm)
+DWTT-71047	54 in  (137.16 cm)
+DWTT-71049	54 in  (137.16 cm)
+DWTT-71078	53 in  (134.62 cm)
+DWTT-71079	53 in  (134.62 cm)
+DWTT-71081	53 in  (134.62 cm)
+DWTT-71083	53 in  (134.62 cm)
+DWTT-71092	54 in  (137.16 cm)
+DWTT-71097	54 in  (137.16 cm)
+DWTT-71098	54 in  (137.16 cm)
+DWTT-71099	54 in  (137.16 cm)
+DWTT-80752	27 in  (68.58 cm)
+DWTT-80755	27 in  (68.58 cm)
+DWTT-80756	27 in  (68.58 cm)
+DWTT-70251	27 in  (68.58 cm)
+DWTT-80758	27 in  (68.58 cm)
+DWTT-80759	27 in  (68.58 cm)
+DWTT-80760	27 in  (68.58 cm)
+DWTT-80769	27 in  (68.58 cm)
+DWTT-80776	27 in  (68.58 cm)
+DWTT-80780	27 in  (68.58 cm)
+DWTT-80657	27 in  (68.58 cm)
+DWTT-70581	27" x 4.5yds
+DWTT-70582	27" x 4.5yds
+DWTT-80681	27 in  (68.58 cm)
+DWTT-70554	27" x 4.5yds
+DWTT-80706	27
+DWTT-80716	27
diff --git a/shopify/scripts/tk11534-doubleroll/widthmap-wallquest.txt b/shopify/scripts/tk11534-doubleroll/widthmap-wallquest.txt
new file mode 100644
index 00000000..dcece404
--- /dev/null
+++ b/shopify/scripts/tk11534-doubleroll/widthmap-wallquest.txt
@@ -0,0 +1,25 @@
+DWQW-57999	27 in | 68.5 cm
+DWQW-57522	20.5 in | 52.1 cm
+DWQW-57523	20.5 in | 52.1 cm
+DWQW-57525	20.5 in | 52.1 cm
+DWQW-57526	20.5 in | 52.1 cm
+DWQW-57245	20.5 in | 52.1 cm
+DWQW-57247	20.5 in | 52.1 cm
+DWQW-57248	20.5 in | 52.1 cm
+DWQW-57502	20.5 in | 52.1 cm
+DWQW-57503	20.5 in | 52.1 cm
+DWQW-57504	20.5 in | 52.1 cm
+DWQW-57506	20.5 in | 52.1 cm
+DWQW-57217	27 in | 68.5 cm
+DWQW-57218	27 in | 68.5 cm
+DWQW-57219	27 in | 68.5 cm
+DWQW-57224	20.5 in | 52.1 cm
+DWQW-57225	20.5 in | 52.1 cm
+DWQW-57099	27 in | 68.5 cm
+DWQW-57100	27 in | 68.5 cm
+DWQW-57101	27 in | 68.5 cm
+DWQW-57103	27 in | 68.5 cm
+DWQW-57125	27 in | 68.5 cm
+DWQW-57126	27 in | 68.5 cm
+DWQW-57127	27 in | 68.5 cm
+DWQW-57128	27 in | 68.5 cm

← fb16e055 auto-data-snapshot: 2026-09-12T23:45:38 (5 data files) — DW-  ·  back to Designer Wallcoverings  ·  TK-11534: make doubleroll rollback self-contained (self-rela 40beee57 →