[object Object]

← back to Designerwallcoverings

snapshot before restart: preserve in-flight work (auto-saved by /restart pre-reboot)

09fa48a6060c5b35964824514123d55248bf789a · 2026-08-17 06:52:32 -0700 · Steve

Files touched

Diff

commit 09fa48a6060c5b35964824514123d55248bf789a
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Aug 17 06:52:32 2026 -0700

    snapshot before restart: preserve in-flight work (auto-saved by /restart pre-reboot)
---
 scripts/greenland-onboard/inspect-miyago-meta.mjs | 31 +++++++++
 scripts/greenland-onboard/inspect-miyago.mjs      | 37 ++++++++++
 scripts/tk10349-flush-page-cache.sh               | 35 ++++++++++
 shopify/scripts/js-sample-leak-scan.mjs           | 83 +++++++++++++++++++++++
 shopify/scripts/reprice-knoll15-belowcost.mjs     | 59 ++++++++++++++++
 shopify/scripts/reprice-med-956-below-map.mjs     | 59 ++++++++++++++++
 6 files changed, 304 insertions(+)

diff --git a/scripts/greenland-onboard/inspect-miyago-meta.mjs b/scripts/greenland-onboard/inspect-miyago-meta.mjs
new file mode 100644
index 0000000..9c574fa
--- /dev/null
+++ b/scripts/greenland-onboard/inspect-miyago-meta.mjs
@@ -0,0 +1,31 @@
+#!/usr/bin/env node
+/** READ-ONLY: metafields + variant min/step for the 6 ACTIVE Mr. Miyago products + siblings. */
+import { gql } from '../lib/shopify.mjs';
+
+const IDS = {
+  'Cork-216222': '6672902553651',
+  'Cork-216223': '6672899768371',
+  'Cork-216224': '6672899866675',
+  'Cork-216225': '6672900063283',
+  'Cork-216226': '6672900161587',
+  'Cork-216227': '6672900325427',
+};
+
+for (const [sku, id] of Object.entries(IDS)) {
+  const q = `{ product(id:"gid://shopify/Product/${id}"){
+    title status
+    metafields(first:30){ nodes{ namespace key value } }
+    variants(first:5){ nodes{ sku title price
+      metafields(first:20){ nodes{ namespace key value } } } }
+  }}`;
+  const d = await gql(q);
+  if (d?.__err) { console.log(sku, 'ERR', JSON.stringify(d.__err)); continue; }
+  const p = d.product;
+  console.log(`\n### ${sku} — ${p.title} [${p.status}]`);
+  const pm = p.metafields.nodes.filter(m => /quantity_order|Brand|width|unit_of_measure|manufacturer_sku|brand|color/i.test(m.key));
+  console.log('  product-metafields:', pm.map(m => `${m.namespace}.${m.key}=${m.value}`).join(' | ') || '(none of interest)');
+  for (const v of p.variants.nodes) {
+    const vm = v.metafields.nodes.filter(m => /quantity_order/i.test(m.key));
+    console.log(`  variant ${v.sku} [${v.title}] $${v.price}  ${vm.map(m => `${m.namespace}.${m.key}=${m.value}`).join(' | ') || '(no qty metafields)'}`);
+  }
+}
diff --git a/scripts/greenland-onboard/inspect-miyago.mjs b/scripts/greenland-onboard/inspect-miyago.mjs
new file mode 100644
index 0000000..6daec6f
--- /dev/null
+++ b/scripts/greenland-onboard/inspect-miyago.mjs
@@ -0,0 +1,37 @@
+#!/usr/bin/env node
+/**
+ * READ-ONLY inspection of the Mr. Miyago cork DRAFT samples Cork-216222..216227.
+ * Searches Shopify by variant SKU, dumps full record shape for the go/no-go decision.
+ *   node inspect-miyago.mjs
+ */
+import { gql } from '../lib/shopify.mjs';
+
+const SKUS = ['Cork-216222', 'Cork-216223', 'Cork-216224', 'Cork-216225', 'Cork-216226', 'Cork-216227'];
+
+async function findBySku(sku) {
+  const q = `query($q:String!){
+    products(first:5, query:$q){
+      nodes{
+        id legacyResourceId title handle status vendor productType tags
+        descriptionHtml
+        onlineStoreUrl
+        publishedAt
+        featuredImage{ url altText }
+        images(first:10){ nodes{ id url altText } }
+        variants(first:25){ nodes{ id sku title price inventoryQuantity inventoryPolicy inventoryItem{ tracked } } }
+        collections(first:20){ nodes{ title handle } }
+        resourcePublicationsCount{ count }
+      }
+    }
+  }`;
+  const d = await gql(q, { q: `sku:${sku}` });
+  if (d?.__err) { console.error(sku, 'ERR', JSON.stringify(d.__err)); return []; }
+  return d.products.nodes;
+}
+
+const out = {};
+for (const sku of SKUS) {
+  const nodes = await findBySku(sku);
+  out[sku] = nodes;
+}
+console.log(JSON.stringify(out, null, 2));
diff --git a/scripts/tk10349-flush-page-cache.sh b/scripts/tk10349-flush-page-cache.sh
new file mode 100644
index 0000000..730e4b3
--- /dev/null
+++ b/scripts/tk10349-flush-page-cache.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+# TK-10349 — flush Shopify page cache for the 5 category pages so the ALREADY-DEPLOYED
+# corrected templates render for customers. The live theme assets are confirmed fixed
+# (!= blank guard + 5 mislabel repoints, updated 2026-08-08 07:48); Shopify's page cache
+# is holding stale HTML that template edits didn't bust. Re-saving each Page record
+# (no-op template_suffix set) bumps updated_at and invalidates that page's cache.
+# Safe: no content change, reversible. Verifies convergence + reports.
+set -euo pipefail
+cd ~/Projects/secrets-manager
+FA=$(grep -E '^SHOPIFY_FULL_ACCESS_TOKEN=' .env | cut -d= -f2- | tr -d '"'"'"' \r')
+HOST="designer-laboratory-sandbox.myshopify.com"; API="2024-10"
+# handle -> page id : template_suffix
+declare -A PID=( [shop-by-material]=182153838643 [brands]=208010608691 [designer-wallpaper-styles]=176660709427 [collections]=27297808449 [commercial-wallcoverings]=182116286515 )
+declare -A SUF=( [shop-by-material]=material [brands]=brands [designer-wallpaper-styles]=styles [collections]=collections [commercial-wallcoverings]=commercial )
+echo ">> Touching 5 pages to flush cache..."
+for h in shop-by-material brands designer-wallpaper-styles collections commercial-wallcoverings; do
+  code=$(curl -s -o /dev/null -w '%{http_code}' -X PUT "https://$HOST/admin/api/$API/pages/${PID[$h]}.json" \
+    -H "X-Shopify-Access-Token: $FA" -H 'Content-Type: application/json' \
+    -d "{\"page\":{\"id\":${PID[$h]},\"template_suffix\":\"${SUF[$h]}\"}}")
+  echo "   $h -> HTTP $code"
+done
+echo ">> Verifying (polls up to ~2.5 min)..."
+UA='Mozilla/5.0 AppleWebKit/537.36 Chrome/120'
+for i in $(seq 1 10); do
+  sleep 15
+  tot=0
+  for p in brands collections designer-wallpaper-styles; do
+    n=$(curl -s -A "$UA" "https://designerwallcoverings.com/pages/$p?_x=$RANDOM$i" | grep -c 'Liquid error' || true)
+    tot=$((tot+n))
+  done
+  jute=$(curl -s -A "$UA" "https://designerwallcoverings.com/pages/shop-by-material?_x=$RANDOM$i" | grep -c '/collections/jute"' || true)
+  echo "   attempt $i: liquidErrors(3 pages)=$tot  material-jute-new=$jute"
+  if [ "$tot" -eq 0 ] && [ "$jute" -gt 0 ]; then echo ">> ✅ CONVERGED — customers now see the fix."; exit 0; fi
+done
+echo ">> Not fully converged yet; page cache may need a few more minutes. Re-run to re-check."
diff --git a/shopify/scripts/js-sample-leak-scan.mjs b/shopify/scripts/js-sample-leak-scan.mjs
new file mode 100644
index 0000000..f5c752a
--- /dev/null
+++ b/shopify/scripts/js-sample-leak-scan.mjs
@@ -0,0 +1,83 @@
+#!/usr/bin/env node
+// TK (sample-price-as-sellable leak) — enumerate Jeffrey Stevens SELLABLE variants priced $4.25
+// (the memo-sample price leaking as the real roll price → sold at a loss + GMC $4.25 disapproval).
+// READ-ONLY by default. --apply reprices to the real roll price (jeffrey_stevens_catalog.price_retail).
+// --limit N caps how many are repriced (canary). Reversibility ledger records prior price.
+// Store: designer-laboratory-sandbox.myshopify.com (LIVE). Vendor scoped to Jeffrey Stevens only.
+import fs from 'node:fs';
+import { execSync } from 'node:child_process';
+
+const APPLY = process.argv.includes('--apply');
+const LIMIT = (()=>{ const i=process.argv.indexOf('--limit'); return i>=0?parseInt(process.argv[i+1]):Infinity; })();
+const TT = execSync("grep -m1 '^SHOPIFY_ADMIN_TOKEN=' ~/Projects/secrets-manager/.env | cut -d= -f2-", {shell:'/bin/zsh'}).toString().trim();
+const PSQL = '/opt/homebrew/Cellar/postgresql@14/14.23/bin/psql';
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+const DIR = '/Users/macstudio3/Projects/designerwallcoverings/shopify/scripts/data/js-sample-leak';
+fs.mkdirSync(DIR, {recursive:true});
+const LEDGER = `${DIR}/js-sample-leak-ledger-2026-08-09.json`;
+const FOUND  = `${DIR}/js-sample-leak-found-2026-08-09.csv`;
+
+const sleep = ms => new Promise(r=>setTimeout(r,ms));
+async function gql(query, variables){
+  for(let a=0;a<6;a++){
+    const r = await fetch(`https://${SHOP}/admin/api/${API}/graphql.json`,{method:'POST',
+      headers:{'X-Shopify-Access-Token':TT,'Content-Type':'application/json'},
+      body:JSON.stringify({query,variables})});
+    const j = await r.json();
+    if(j.errors && JSON.stringify(j.errors).includes('THROTTLED')){ await sleep(2000); continue; }
+    const th=j?.extensions?.cost?.throttleStatus; if(th && th.currentlyAvailable<400){ await sleep(1500); }
+    return j;
+  }
+  throw new Error('gql retries exhausted');
+}
+// real roll price per SKU from the catalog (price_retail)
+function realPrice(sku){
+  try{ const out=execSync(`${PSQL} -h /tmp -d dw_unified -tAc "select price_retail from jeffrey_stevens_catalog where sku='${sku.replace(/'/g,"''")}' and price_retail is not null limit 1"`).toString().trim();
+    const v=parseFloat(out); return isFinite(v)&&v>4.25?v:null; }catch{ return null; }
+}
+
+const Q = `query($cursor:String){products(first:40,query:"vendor:'Jeffrey Stevens' status:active",after:$cursor){
+  pageInfo{hasNextPage endCursor} edges{node{id title
+    variants(first:20){edges{node{id sku title price}}}}}}}`;
+
+const leaks=[]; let scanned=0, cursor=null, hasNext=true;
+while(hasNext){
+  const d = await gql(Q,{cursor});
+  const conn = d?.data?.products; if(!conn){ console.error('ERR',JSON.stringify(d).slice(0,300)); break; }
+  for(const e of conn.edges){
+    scanned++;
+    for(const ve of e.node.variants.edges){
+      const v=ve.node; const isSample=/sample/i.test(v.sku||'')||/sample/i.test(v.title||'');
+      if(!isSample && parseFloat(v.price)===4.25){
+        leaks.push({product:e.node.title, sku:v.sku, variantTitle:v.title, price:4.25,
+                    variant_id:v.id, product_id:e.node.id, real:realPrice(v.sku)});
+      }
+    }
+  }
+  hasNext = conn.pageInfo.hasNextPage; cursor = conn.pageInfo.endCursor;
+}
+
+fs.writeFileSync(FOUND, 'sku,product,variantTitle,real_price\n'+leaks.map(l=>`${l.sku},"${l.product}","${l.variantTitle}",${l.real??''}`).join('\n'));
+const withReal = leaks.filter(l=>l.real!=null);
+console.log(`SCANNED ${scanned} JS active products | LEAKS(sellable @ $4.25)=${leaks.length} | with real price_retail=${withReal.length} | missing real=${leaks.length-withReal.length}`);
+console.log(`found list: ${FOUND}`);
+
+if(!APPLY){ console.log('READ-ONLY (no --apply). Nothing written.'); process.exit(0); }
+
+const MUT=`mutation($productId:ID!,$variants:[ProductVariantsBulkInput!]!){productVariantsBulkUpdate(productId:$productId,variants:$variants){productVariants{id price}userErrors{field message}}}`;
+const ledger=[]; let fixed=0, skipped_noreal=0, failed=0, capped=0;
+for(const l of leaks){
+  if(l.real==null){ skipped_noreal++; ledger.push({sku:l.sku,action:'SKIP_NO_REAL_PRICE'}); continue; }
+  if(fixed>=LIMIT){ capped++; continue; }
+  try{
+    const res=await gql(MUT,{productId:l.product_id, variants:[{id:l.variant_id, price:l.real.toFixed(2)}]});
+    const ue=res?.data?.productVariantsBulkUpdate?.userErrors||[];
+    const np=res?.data?.productVariantsBulkUpdate?.productVariants?.[0]?.price;
+    if(ue.length===0 && np && Math.abs(parseFloat(np)-l.real)<=0.01){ fixed++; ledger.push({sku:l.sku,action:'FIXED',prior:4.25,new:l.real,variant_id:l.variant_id.split('/').pop()}); }
+    else { failed++; ledger.push({sku:l.sku,action:'FAIL',userErrors:ue,np}); }
+  }catch(err){ failed++; ledger.push({sku:l.sku,action:'ERROR',error:String(err)}); }
+}
+fs.writeFileSync(LEDGER, JSON.stringify({ts:'2026-08-09',ticket:'js-sample-leak',approved:'Steve email 2026-08-09 work-on-top-3-gated Dust2026',limit:LIMIT,counts:{leaks:leaks.length,fixed,skipped_noreal,failed,capped},results:ledger},null,2));
+console.log(`APPLY done. fixed=${fixed} skipped_noreal=${skipped_noreal} failed=${failed} capped(over --limit)=${capped}`);
+console.log(`ledger: ${LEDGER}`);
diff --git a/shopify/scripts/reprice-knoll15-belowcost.mjs b/shopify/scripts/reprice-knoll15-belowcost.mjs
new file mode 100644
index 0000000..1c00ddf
--- /dev/null
+++ b/shopify/scripts/reprice-knoll15-belowcost.mjs
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+// TK-10014 — raise the 1,141 HIGH_two_source below-MAP DWKK roll variants to MAP on LIVE Shopify.
+// Steve-approved in-session 2026-08-08 ("do the 1,141"). Safeguards: live re-fetch current price,
+// SKIP any already >= MAP, raise rest to MAP, full prior->new reversibility ledger, throttle-aware.
+import fs from 'node:fs';
+import { execSync } from 'node:child_process';
+
+const TT = execSync("grep -m1 '^SHOPIFY_ADMIN_TOKEN=' ~/Projects/secrets-manager/.env | cut -d= -f2-", {shell:'/bin/zsh'}).toString().trim();
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+const DIR = '/Users/macstudio3/Projects/designerwallcoverings/shopify/scripts/data/knoll';
+const LEDGER = `${DIR}/knoll15-belowcost-reprice-ledger-2026-08-09.json`;
+const LOG = `${DIR}/knoll15-belowcost-progress.log`;
+
+const rows = fs.readFileSync('/tmp/knoll15.csv','utf8').trim().split('\n').map(l=>{const[sku,map]=l.split(',');return{sku,map:parseFloat(map)};});
+const log = (m)=>{ const line=`[${new Date().toISOString()}] ${m}`; fs.appendFileSync(LOG, line+'\n'); console.log(line); };
+
+async function gql(query, variables){
+  for(let attempt=0; attempt<6; attempt++){
+    const r = await fetch(`https://${SHOP}/admin/api/${API}/graphql.json`, {method:'POST',
+      headers:{'X-Shopify-Access-Token':TT,'Content-Type':'application/json'},
+      body: JSON.stringify({query, variables})});
+    const j = await r.json();
+    const th = j?.extensions?.cost?.throttleStatus;
+    if(j.errors && JSON.stringify(j.errors).includes('THROTTLED')){ await sleep(2000); continue; }
+    if(th && th.currentlyAvailable < 600){ await sleep(1500); }
+    return j;
+  }
+  throw new Error('gql retries exhausted');
+}
+const sleep = ms => new Promise(r=>setTimeout(r,ms));
+
+const READ = `query($q:String!){productVariants(first:10,query:$q){edges{node{id sku price product{id status}}}}}`;
+const MUT  = `mutation($productId:ID!,$variants:[ProductVariantsBulkInput!]!){productVariantsBulkUpdate(productId:$productId,variants:$variants){productVariants{id price}userErrors{field message}}}`;
+
+const ledger=[]; let raised=0, skipped_atmap=0, skipped_inactive=0, notfound=0, failed=0;
+
+for(let i=0;i<rows.length;i++){
+  const t = rows[i];
+  try{
+    const d = await gql(READ,{q:`sku:${t.sku}`});
+    const edges = d?.data?.productVariants?.edges || [];
+    const roll = edges.find(e=>e.node.sku===t.sku);
+    if(!roll){ notfound++; ledger.push({sku:t.sku,action:'NOT_FOUND'}); continue; }
+    const n=roll.node, cur=parseFloat(n.price), pid=n.product.id, vid=n.id;
+    if(n.product.status!=='ACTIVE'){ skipped_inactive++; ledger.push({sku:t.sku,action:'SKIP_INACTIVE',status:n.product.status,prior:cur}); continue; }
+    if(cur >= t.map-0.01){ skipped_atmap++; ledger.push({sku:t.sku,action:'SKIP_ALREADY_GE_MAP',prior:cur,map:t.map}); continue; }
+    const res = await gql(MUT,{productId:pid, variants:[{id:vid, price:t.map.toFixed(2)}]});
+    const ue = res?.data?.productVariantsBulkUpdate?.userErrors || [];
+    const newp = res?.data?.productVariantsBulkUpdate?.productVariants?.[0]?.price;
+    const ok = ue.length===0 && newp!=null && Math.abs(parseFloat(newp)-t.map)<=0.01;
+    if(ok){ raised++; ledger.push({sku:t.sku,variant_id:vid.split('/').pop(),action:'RAISED',prior:cur,new:parseFloat(newp),map:t.map}); }
+    else { failed++; ledger.push({sku:t.sku,action:'FAIL',prior:cur,map:t.map,userErrors:ue,newp}); log(`FAIL ${t.sku}: ${JSON.stringify(ue)}`); }
+  }catch(e){ failed++; ledger.push({sku:t.sku,action:'ERROR',error:String(e)}); log(`ERROR ${t.sku}: ${e}`); }
+  if((i+1)%50===0){ log(`progress ${i+1}/${rows.length} | raised=${raised} skip_atmap=${skipped_atmap} skip_inactive=${skipped_inactive} notfound=${notfound} fail=${failed}`); fs.writeFileSync(LEDGER, JSON.stringify({ts:'2026-08-08',ticket:'TK-10302-belowcost',approved:'Steve email 2026-08-09 work-on-top-3-gated Dust2026',partial:true,counts:{raised,skipped_atmap,skipped_inactive,notfound,failed},results:ledger},null,2)); }
+}
+fs.writeFileSync(LEDGER, JSON.stringify({ts:'2026-08-08',ticket:'TK-10302-belowcost',approved:'Steve email 2026-08-09 work-on-top-3-gated Dust2026',partial:false,counts:{raised,skipped_atmap,skipped_inactive,notfound,failed,total:rows.length},results:ledger},null,2));
+log(`DONE. total=${rows.length} raised=${raised} skip_atmap=${skipped_atmap} skip_inactive=${skipped_inactive} notfound=${notfound} fail=${failed}`);
+log(`Ledger: ${LEDGER}`);
diff --git a/shopify/scripts/reprice-med-956-below-map.mjs b/shopify/scripts/reprice-med-956-below-map.mjs
new file mode 100644
index 0000000..b02528b
--- /dev/null
+++ b/shopify/scripts/reprice-med-956-below-map.mjs
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+// TK-10014 — raise the 1,141 HIGH_two_source below-MAP DWKK roll variants to MAP on LIVE Shopify.
+// Steve-approved in-session 2026-08-08 ("do the 1,141"). Safeguards: live re-fetch current price,
+// SKIP any already >= MAP, raise rest to MAP, full prior->new reversibility ledger, throttle-aware.
+import fs from 'node:fs';
+import { execSync } from 'node:child_process';
+
+const TT = execSync("grep -m1 '^SHOPIFY_ADMIN_TOKEN=' ~/Projects/secrets-manager/.env | cut -d= -f2-", {shell:'/bin/zsh'}).toString().trim();
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+const DIR = '/Users/macstudio3/Projects/designerwallcoverings/shopify/scripts/data/kravet-map-pricing';
+const LEDGER = `${DIR}/med-579-reprice-ledger-2026-08-09.json`;
+const LOG = `${DIR}/med-579-reprice-progress.log`;
+
+const rows = fs.readFileSync('/tmp/MEDFILE.csv','utf8').trim().split('\n').map(l=>{const[sku,map]=l.split(',');return{sku,map:parseFloat(map)};});
+const log = (m)=>{ const line=`[${new Date().toISOString()}] ${m}`; fs.appendFileSync(LOG, line+'\n'); console.log(line); };
+
+async function gql(query, variables){
+  for(let attempt=0; attempt<6; attempt++){
+    const r = await fetch(`https://${SHOP}/admin/api/${API}/graphql.json`, {method:'POST',
+      headers:{'X-Shopify-Access-Token':TT,'Content-Type':'application/json'},
+      body: JSON.stringify({query, variables})});
+    const j = await r.json();
+    const th = j?.extensions?.cost?.throttleStatus;
+    if(j.errors && JSON.stringify(j.errors).includes('THROTTLED')){ await sleep(2000); continue; }
+    if(th && th.currentlyAvailable < 600){ await sleep(1500); }
+    return j;
+  }
+  throw new Error('gql retries exhausted');
+}
+const sleep = ms => new Promise(r=>setTimeout(r,ms));
+
+const READ = `query($q:String!){productVariants(first:10,query:$q){edges{node{id sku price product{id status}}}}}`;
+const MUT  = `mutation($productId:ID!,$variants:[ProductVariantsBulkInput!]!){productVariantsBulkUpdate(productId:$productId,variants:$variants){productVariants{id price}userErrors{field message}}}`;
+
+const ledger=[]; let raised=0, skipped_atmap=0, skipped_inactive=0, notfound=0, failed=0;
+
+for(let i=0;i<rows.length;i++){
+  const t = rows[i];
+  try{
+    const d = await gql(READ,{q:`sku:${t.sku}`});
+    const edges = d?.data?.productVariants?.edges || [];
+    const roll = edges.find(e=>e.node.sku===t.sku);
+    if(!roll){ notfound++; ledger.push({sku:t.sku,action:'NOT_FOUND'}); continue; }
+    const n=roll.node, cur=parseFloat(n.price), pid=n.product.id, vid=n.id;
+    if(n.product.status!=='ACTIVE'){ skipped_inactive++; ledger.push({sku:t.sku,action:'SKIP_INACTIVE',status:n.product.status,prior:cur}); continue; }
+    if(cur >= t.map-0.01){ skipped_atmap++; ledger.push({sku:t.sku,action:'SKIP_ALREADY_GE_MAP',prior:cur,map:t.map}); continue; }
+    const res = await gql(MUT,{productId:pid, variants:[{id:vid, price:t.map.toFixed(2)}]});
+    const ue = res?.data?.productVariantsBulkUpdate?.userErrors || [];
+    const newp = res?.data?.productVariantsBulkUpdate?.productVariants?.[0]?.price;
+    const ok = ue.length===0 && newp!=null && Math.abs(parseFloat(newp)-t.map)<=0.01;
+    if(ok){ raised++; ledger.push({sku:t.sku,variant_id:vid.split('/').pop(),action:'RAISED',prior:cur,new:parseFloat(newp),map:t.map}); }
+    else { failed++; ledger.push({sku:t.sku,action:'FAIL',prior:cur,map:t.map,userErrors:ue,newp}); log(`FAIL ${t.sku}: ${JSON.stringify(ue)}`); }
+  }catch(e){ failed++; ledger.push({sku:t.sku,action:'ERROR',error:String(e)}); log(`ERROR ${t.sku}: ${e}`); }
+  if((i+1)%50===0){ log(`progress ${i+1}/${rows.length} | raised=${raised} skip_atmap=${skipped_atmap} skip_inactive=${skipped_inactive} notfound=${notfound} fail=${failed}`); fs.writeFileSync(LEDGER, JSON.stringify({ts:'2026-08-08',ticket:'TK-10014',approved:'Steve email 2026-08-09 Dust2026 go continue (MED 579)',partial:true,counts:{raised,skipped_atmap,skipped_inactive,notfound,failed},results:ledger},null,2)); }
+}
+fs.writeFileSync(LEDGER, JSON.stringify({ts:'2026-08-08',ticket:'TK-10014',approved:'Steve email 2026-08-09 Dust2026 go continue (MED 579)',partial:false,counts:{raised,skipped_atmap,skipped_inactive,notfound,failed,total:rows.length},results:ledger},null,2));
+log(`DONE. total=${rows.length} raised=${raised} skip_atmap=${skipped_atmap} skip_inactive=${skipped_inactive} notfound=${notfound} fail=${failed}`);
+log(`Ledger: ${LEDGER}`);

← 12c12d4 auto-data-snapshot: 2026-08-17T04:29:37 (1 data files) — dat  ·  back to Designerwallcoverings  ·  auto-data-snapshot: 2026-08-17T08:03:25 (2 data files) — scr 1d601f8 →