← back to Designerwallcoverings
TK-10895: reprice/revert tool for the 5 products priced off the mill sheet FABRIC row
4816e02d8bec2a3e24e2e51717c2fb88b46cddaa · 2026-09-10 10:43:12 -0700 · Steve
Storefront-verified: 23-28% customer overcharge. Two modes so the choice stays
Steve's - reprice to the WP-row-derived retail, or revert to sample-only and
assert no new price. Verifies on the RENDERED STOREFRONT after each write, never
the Admin API. DRY-RUN by default.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Files touched
A scripts/tk10895-reprice-fabricrow5.mjs
Diff
commit 4816e02d8bec2a3e24e2e51717c2fb88b46cddaa
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Sep 10 10:43:12 2026 -0700
TK-10895: reprice/revert tool for the 5 products priced off the mill sheet FABRIC row
Storefront-verified: 23-28% customer overcharge. Two modes so the choice stays
Steve's - reprice to the WP-row-derived retail, or revert to sample-only and
assert no new price. Verifies on the RENDERED STOREFRONT after each write, never
the Admin API. DRY-RUN by default.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---
scripts/tk10895-reprice-fabricrow5.mjs | 87 ++++++++++++++++++++++++++++++++++
1 file changed, 87 insertions(+)
diff --git a/scripts/tk10895-reprice-fabricrow5.mjs b/scripts/tk10895-reprice-fabricrow5.mjs
new file mode 100644
index 0000000..7c4eef0
--- /dev/null
+++ b/scripts/tk10895-reprice-fabricrow5.mjs
@@ -0,0 +1,87 @@
+#!/usr/bin/env node
+// TK-10895 — the 5 LIVE products priced off the mill sheet's FABRIC row instead of its WP twin.
+//
+// The mill sheet carries TWO rows per pattern at DIFFERENT costs. Our catalog's 6-digit mfr_sku
+// sits on the dearer FABRIC row, so the loaded cost is the fabric's. Customers are overcharged
+// 23-28% right now. Verified on the RENDERED STOREFRONT (not the Admin API - that lies about
+// variant order on this store, see TK-10895 2026-09-04 and again today).
+//
+// THE PROOF, not the inference: 709221 is labelled "Bijou Stripe FABRIC", typed Wallpaper, 27" wide,
+// and carries $183 - the identical cost to 901622, the genuine Bijou Stripe FABRIC row typed Fabric
+// at 54". A 27" wallpaper wearing the 54" fabric's exact cost.
+//
+// Modes: --mode reprice set the sellable variant to the WP-cost-derived retail (default)
+// --mode revert delete the sellable variant, back to sample-only (asserts no new price)
+// DRY-RUN by default. --apply writes.
+
+import fs from 'fs';
+const SHOP='designer-laboratory-sandbox.myshopify.com', VER='2024-10';
+const TOK=fs.readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8')
+ .match(/^SHOPIFY_FULL_ACCESS_TOKEN=(.+)$/m)[1].trim().replace(/^["']|["']$/g,'');
+const APPLY=process.argv.includes('--apply');
+const MODE=(()=>{const i=process.argv.indexOf('--mode');return i>-1?process.argv[i+1]:'reprice';})();
+const MAP=process.env.HOME+'/.claude/yolo-queue/executed-reversible/TK-10895-fabricrow5.jsonl';
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+
+// mfr -> {pattern, wpCost, wpRow, fabCost}
+const T={
+ '708421':{p:'Barbados Batik', wp:145, row:'8250-05WP', fab:178},
+ '708521':{p:'Barbados Batik', wp:145, row:'8250-05WP', fab:178},
+ '709221':{p:'Bijou Stripe', wp:148, row:'5060-05WP', fab:183},
+ '709321':{p:'Birds II', wp:142, row:'5050-04WP', fab:182},
+ '709421':{p:'Birds II', wp:142, row:'5050-04WP', fab:182},
+};
+const retail=c=>Math.round((c/0.65/0.85)*100)/100;
+
+async function api(path,opts={},tries=6){
+ for(let a=1;a<=tries;a++){
+ try{const r=await fetch(`https://${SHOP}/admin/api/${VER}${path}`,{...opts,
+ headers:{'X-Shopify-Access-Token':TOK,'Content-Type':'application/json',...(opts.headers||{})}});
+ if(r.status===429||r.status>=500){await sleep(1500*a);continue;}
+ const t=await r.text(); try{return{status:r.status,body:JSON.parse(t)};}catch{await sleep(1200*a);}
+ }catch{await sleep(1200*a);} }
+ return {status:0,body:null};
+}
+// storefront is the ORACLE for what the customer sees
+async function storefront(handle){
+ for(let a=1;a<=4;a++){
+ try{const r=await fetch(`https://designerwallcoverings.com/products/${handle}.json`);
+ if(r.ok) return (await r.json()).product;
+ }catch{} await sleep(1500*a); }
+ return null;
+}
+
+console.log(`${APPLY?'*** APPLY — LIVE WRITES ***':'DRY-RUN'} — mode=${MODE} — ${Object.keys(T).length} products\n`);
+let done=0,hard=0;
+for(const [mfr,t] of Object.entries(T)){
+ const q=await api(`/products.json?limit=1&handle=dwcw-${mfr}`);
+ const p=q.body?.products?.[0];
+ if(!p){console.log(`${mfr} FAIL — product not found`);hard++;continue;}
+ const sell=p.variants.filter(v=>parseFloat(v.price)>50);
+ if(sell.length!==1){console.log(`${mfr} SKIP — ${sell.length} sellable variants, expected 1`);continue;}
+ const v=sell[0], want=retail(t.wp), now=parseFloat(v.price);
+ if(MODE==='reprice' && Math.abs(now-want)<0.01){console.log(`${mfr} already $${want} — nothing to do`);continue;}
+
+ const line = MODE==='revert'
+ ? `${mfr} ${t.p.padEnd(15)} would DELETE variant ${v.id} ($${now}) -> back to sample-only`
+ : `${mfr} ${t.p.padEnd(15)} $${now} -> $${want} (WP row ${t.row} @ $${t.wp} vs FABRIC $${t.fab}, saves customer $${(now-want).toFixed(2)})`;
+ if(!APPLY){console.log(line);done++;continue;}
+
+ let res;
+ if(MODE==='revert') res=await api(`/products/${p.id}/variants/${v.id}.json`,{method:'DELETE'});
+ else res=await api(`/variants/${v.id}.json`,{method:'PUT',body:JSON.stringify({variant:{id:v.id,price:want.toFixed(2)}})});
+ if(res.status>=400||res.status===0){console.log(`${mfr} HARD FAIL ${res.status}`);hard++;continue;}
+
+ await sleep(800);
+ const sf=await storefront(p.handle); // VERIFY on the oracle, not the Admin API
+ const live=sf?sf.variants.filter(x=>parseFloat(x.price)>50):null;
+ const okNow = MODE==='revert' ? (live && live.length===0)
+ : (live && live.length===1 && Math.abs(parseFloat(live[0].price)-want)<0.01);
+ if(!okNow){console.log(`${mfr} HARD FAIL — storefront does not reflect the change`);hard++;continue;}
+ fs.appendFileSync(MAP,JSON.stringify({ts:new Date().toISOString(),ticket:'TK-10895',mode:MODE,
+ mfr,handle:p.handle,product_id:p.id,variant_id:v.id,before_price:now,after_price:MODE==='revert'?null:want,
+ undo:MODE==='revert'?`re-create variant at $${now}`:`PUT /variants/${v.id}.json price=${now}`})+'\n');
+ done++; console.log(`${mfr} ✓ ${line}`);
+}
+console.log(`\nDONE ${MODE}=${done} hardFail=${hard}`);
+if(hard) process.exitCode=1;
← b0416ab TK-10895: table-driven reprice + CDN grace period
·
back to Designerwallcoverings
·
TK-10895: post-verify must read the STOREFRONT, not the Admi e0c030e →