[object Object]

← back to Dw Yolo Loop

Kravet MAP revert tool (DTD verdict B): restore the 448 written prices to original

182a36851644296953f613eb6aa1dcaffa586427 · 2026-06-15 09:18:37 -0700 · Steve Abrams

DTD 3/3 found a units/granularity defect in the MAP table (~3x gap split across the
2.5x guard). Apply was killed mid-flight at 448/998. This detects written-vs-not by
live price and restores originals. Reversible/restorative.

Files touched

Diff

commit 182a36851644296953f613eb6aa1dcaffa586427
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 15 09:18:37 2026 -0700

    Kravet MAP revert tool (DTD verdict B): restore the 448 written prices to original
    
    DTD 3/3 found a units/granularity defect in the MAP table (~3x gap split across the
    2.5x guard). Apply was killed mid-flight at 448/998. This detects written-vs-not by
    live price and restores originals. Reversible/restorative.
---
 scripts/kravet-cost/revert-map-push.mjs | 52 +++++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/scripts/kravet-cost/revert-map-push.mjs b/scripts/kravet-cost/revert-map-push.mjs
new file mode 100644
index 0000000..6557e30
--- /dev/null
+++ b/scripts/kravet-cost/revert-map-push.mjs
@@ -0,0 +1,52 @@
+#!/usr/bin/env node
+/**
+ * revert-map-push.mjs — undo the MAP push: restore every product whose roll price
+ * was changed to MAP back to its ORIGINAL price (status-quo-ante).
+ *
+ * Why: DTD verdict (3/3, 2026-06-15) = the MAP table has a units/granularity defect
+ * (~3x gap, split across the 2.5x guard), so the 998 push cannot be trusted. This
+ * restores the prior live prices. Re-detects what was actually written by reading the
+ * live roll price (== MAP => was written; == original => skip). Reversible, restorative.
+ *
+ * DRY-RUN default; LIVE requires --apply --i-am-steve.
+ */
+import fs from 'node:fs';
+const SHOP = 'designer-laboratory-sandbox.myshopify.com', VER = '2024-10';
+const TOKEN = (fs.readFileSync(process.env.HOME + '/Projects/secrets-manager/.env', 'utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1]?.trim();
+const URL = `https://${SHOP}/admin/api/${VER}/graphql.json`;
+const args = Object.fromEntries(process.argv.slice(2).map(a => { const [k, v] = a.replace(/^--/, '').split('='); return [k, v === undefined ? true : v]; }));
+const APPLY = args.apply === true && args['i-am-steve'] === true;
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+async function gql(q, v) { for (let a = 0; a < 8; a++) { let j; try { const r = await fetch(URL, { method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ query: q, variables: v }) }); j = await r.json(); } catch (e) { await sleep(1500 * (a + 1)); continue; } if (j.errors) { if (JSON.stringify(j.errors).includes('THROTTLED')) { await sleep(2000 * (a + 1)); continue; } throw new Error(JSON.stringify(j.errors)); } const t = j.extensions?.cost?.throttleStatus; if (t && t.currentlyAvailable < 400) await sleep(1200); return j.data; } throw new Error('retries'); }
+
+const plan = JSON.parse(fs.readFileSync('data/kravet-cost/price-push-plan.json', 'utf8'));
+const items = plan.toWrite; // {pid, vid, mfr, cur, map}
+console.log(`revert-map-push — mode: ${APPLY ? '⚠️  LIVE REVERT' : 'DRY-RUN'} | candidates: ${items.length}`);
+
+const Q = `query($ids:[ID!]!){ nodes(ids:$ids){ ... on ProductVariant { id price } } }`;
+const M = `mutation($pid:ID!,$variants:[ProductVariantsBulkInput!]!){ productVariantsBulkUpdate(productId:$pid, variants:$variants){ userErrors{ field message } } }`;
+
+// detect which were written (live price == map)
+const written = [];
+for (let i = 0; i < items.length; i += 100) {
+  const batch = items.slice(i, i + 100);
+  const d = await gql(Q, { ids: batch.map(b => b.vid) });
+  const live = new Map(d.nodes.filter(Boolean).map(n => [n.id, parseFloat(n.price)]));
+  for (const it of batch) {
+    const lp = live.get(it.vid);
+    if (lp != null && Math.abs(lp - it.map) < 0.01) written.push(it); // currently at MAP => was written
+  }
+}
+console.log(`detected WRITTEN (live price == MAP): ${written.length} / ${items.length}`);
+console.log('sample reverts:'); for (const w of written.slice(0, 8)) console.log(`  ${w.mfr}: $${w.map} -> restore $${w.cur}`);
+if (!APPLY) { console.log('\nDRY-RUN. To execute: node revert-map-push.mjs --apply --i-am-steve'); process.exit(0); }
+
+console.log('\n⚠️  LIVE REVERT — restoring original prices…');
+let ok = 0, err = 0;
+for (const w of written) {
+  try { const d = await gql(M, { pid: w.pid, variants: [{ id: w.vid, price: w.cur.toFixed(2) }] });
+    const ue = d.productVariantsBulkUpdate?.userErrors || []; if (ue.length) { err++; if (err <= 10) console.log('  err', w.mfr, JSON.stringify(ue)); } else ok++;
+  } catch (e) { err++; if (err <= 10) console.log('  EX', w.mfr, e.message); }
+  if ((ok + err) % 200 === 0) console.log(`  progress ${ok} reverted / ${err} err`);
+}
+console.log(`\nDONE — ${ok} prices restored to original, ${err} errors.`);

← 6f4cc86 Kravet MAP push tool: roll-variant-only price=MAP, dry-run g  ·  back to Dw Yolo Loop  ·  ROOT CAUSE + FIX: Kravet MAP push now unit-aware (ROLL-only f1da716 →