[object Object]

← back to Tk 11331 Exec

TK-11331 D3a: reprice 3 diff sellable variants to hw_price (restore-map + Admin-verify)

362d57e8df7f207016810b2826196b9de28e811e · 2026-09-09 13:49:44 -0700 · Steve Abrams

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

Files touched

Diff

commit 362d57e8df7f207016810b2826196b9de28e811e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 9 13:49:44 2026 -0700

    TK-11331 D3a: reprice 3 diff sellable variants to hw_price (restore-map + Admin-verify)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01X3W2EKx4Yu1hZfiEdQPkQf
---
 d3a_reprice.mjs                | 54 ++++++++++++++++++++++++++++++++++++++++++
 data/d3a-reprice-restore.jsonl |  3 +++
 data/d3a-reprice-results.json  | 17 +++++++++++++
 3 files changed, 74 insertions(+)

diff --git a/d3a_reprice.mjs b/d3a_reprice.mjs
new file mode 100644
index 0000000..687841f
--- /dev/null
+++ b/d3a_reprice.mjs
@@ -0,0 +1,54 @@
+// TK-11331 DECISION-3a — reprice sellable variant price = hw_price for diff rows only.
+// Diff = manifest rows where current_price != new_price. Records restore map BEFORE writing.
+// Does NOT touch the $4.25 Sample variant, its position, or any metafield. --apply to write.
+import fs from 'fs';
+import {gql,sleep} from './lib.mjs';
+const APPLY=process.argv.includes('--apply');
+const MAN='data/d3a-manifest.jsonl';
+const RESTORE='data/d3a-reprice-restore.jsonl';
+const rows=fs.readFileSync(MAN,'utf8').trim().split('\n').map(l=>JSON.parse(l));
+
+// derive diff set: current_price != new_price (skip no-ops)
+const diff=rows.filter(r=>Number(r.current_price)!==Number(r.new_price));
+console.log(`${APPLY?'APPLY':'DRY-RUN'} D3a reprice — diff=${diff.length} of ${rows.length} (skipping ${rows.length-diff.length} no-ops)`);
+
+const VQ=`query($id:ID!){productVariant(id:$id){id sku price product{id title}}}`;
+const M=`mutation($pid:ID!,$variants:[ProductVariantsBulkInput!]!){
+  productVariantsBulkUpdate(productId:$pid,variants:$variants){
+    productVariants{id sku price} userErrors{field message} }}`;
+
+const rstream=APPLY?fs.createWriteStream(RESTORE,{flags:'a'}):null;
+let ok=0,fail=0,skip=0; const fails=[]; const results=[];
+for(const r of diff){
+  const vid=r.variant_id, pid=`gid://shopify/Product/${r.shopify_product_id}`;
+  const np=Number(r.new_price);
+  // authoritative live read right before write
+  const live=(await gql(VQ,{id:vid})).productVariant;
+  if(!live){ fail++; fails.push({sku:r.sku,reason:'variant-not-found'}); console.log(`  ERR ${r.sku} variant not found`); continue; }
+  // safety: must be the sellable variant (not the sample), and its sku must match manifest
+  if((live.sku||'').toLowerCase().endsWith('sample')){ skip++; console.log(`  SKIP ${r.sku} — live variant is a Sample (${live.sku}); refusing to touch`); continue; }
+  if(live.sku!==r.sku){ skip++; console.log(`  SKIP ${r.sku} — live sku mismatch (${live.sku})`); continue; }
+  const before=Number(live.price);
+  if(Math.abs(before-Number(r.current_price))>0.005){
+    console.log(`  WARN ${r.sku} live price ${before} != manifest current ${r.current_price} (recording live as restore value)`);
+  }
+  if(Math.abs(before-np)<0.005){ skip++; console.log(`  SKIP ${r.sku} — already ${np}`); continue; }
+  if(!APPLY){ console.log(`  would reprice ${r.sku}: ${before} -> ${np}`); ok++; continue; }
+  // restore map BEFORE the write
+  rstream.write(JSON.stringify({ts:new Date().toISOString(),sku:live.sku,pid,variant_id:vid,
+    old_price:before,new_price:np,
+    undo:`productVariantsBulkUpdate ${pid} variants:[{id:${vid},price:"${before.toFixed(2)}"}]`})+'\n');
+  const res=await gql(M,{pid,variants:[{id:vid,price:np.toFixed(2)}]});
+  const ue=res.productVariantsBulkUpdate.userErrors;
+  if(ue.length){ fail++; fails.push({sku:r.sku,ue}); console.log(`  ERR ${r.sku}`,JSON.stringify(ue)); continue; }
+  await sleep(400);
+  // verify via authoritative re-read
+  const after=(await gql(VQ,{id:vid})).productVariant;
+  if(Math.abs(Number(after.price)-np)<0.005){ ok++; results.push({sku:after.sku,before,after:Number(after.price)}); console.log(`  OK ${after.sku}: ${before} -> ${after.price} (verified)`); }
+  else { fail++; fails.push({sku:r.sku,reason:'verify-mismatch',got:after.price,want:np}); console.log(`  VERIFY-FAIL ${r.sku} got ${after.price} want ${np}`); }
+  await sleep(150);
+}
+if(rstream) rstream.end();
+console.log(`D3a ${APPLY?'APPLIED':'DRY'} — ok=${ok} fail=${fail} skip=${skip}`);
+if(fails.length) fs.writeFileSync('data/d3a-reprice-fails.json',JSON.stringify(fails,null,1));
+if(results.length) fs.writeFileSync('data/d3a-reprice-results.json',JSON.stringify(results,null,1));
diff --git a/data/d3a-reprice-restore.jsonl b/data/d3a-reprice-restore.jsonl
new file mode 100644
index 0000000..35e2ce2
--- /dev/null
+++ b/data/d3a-reprice-restore.jsonl
@@ -0,0 +1,3 @@
+{"ts":"2026-09-09T20:48:06.114Z","sku":"XMM-44425-yard","pid":"gid://shopify/Product/1496280825968","variant_id":"gid://shopify/ProductVariant/17831059324993","old_price":43.76,"new_price":51.98,"undo":"productVariantsBulkUpdate gid://shopify/Product/1496280825968 variants:[{id:gid://shopify/ProductVariant/17831059324993,price:\"43.76\"}]"}
+{"ts":"2026-09-09T20:48:08.036Z","sku":"XMM-44426-yard","pid":"gid://shopify/Product/1496281055344","variant_id":"gid://shopify/ProductVariant/17831059357761","old_price":43.76,"new_price":51.98,"undo":"productVariantsBulkUpdate gid://shopify/Product/1496281055344 variants:[{id:gid://shopify/ProductVariant/17831059357761,price:\"43.76\"}]"}
+{"ts":"2026-09-09T20:48:10.792Z","sku":"XHW-2010354-yard","pid":"gid://shopify/Product/1501235544176","variant_id":"gid://shopify/ProductVariant/17657146376257","old_price":45.54,"new_price":52.71,"undo":"productVariantsBulkUpdate gid://shopify/Product/1501235544176 variants:[{id:gid://shopify/ProductVariant/17657146376257,price:\"45.54\"}]"}
diff --git a/data/d3a-reprice-results.json b/data/d3a-reprice-results.json
new file mode 100644
index 0000000..3e9b012
--- /dev/null
+++ b/data/d3a-reprice-results.json
@@ -0,0 +1,17 @@
+[
+ {
+  "sku": "XMM-44425-yard",
+  "before": 43.76,
+  "after": 51.98
+ },
+ {
+  "sku": "XMM-44426-yard",
+  "before": 43.76,
+  "after": 51.98
+ },
+ {
+  "sku": "XHW-2010354-yard",
+  "before": 45.54,
+  "after": 52.71
+ }
+]
\ No newline at end of file

← bf6dd77 TK-11331 Day-1 complete: D1 569 reordered + D2 431 backfille  ·  back to Tk 11331 Exec  ·  TK-11331: hands-off D2 daily-sweep automation (wrapper + pli 53413a1 →