← back to Tk10630 Sku Suffix Canary
generator-backfill-dryrun.mjs
48 lines
// Option C backfill DRY-RUN (no writes). For the feed-matched products (deep-recovered.json,
// each has a real Momentum `number`), show what Option C would set:
// manufacturer_sku = number ; dw_sku (global/dwc/custom) = number ; variant = number-sample/-yard
// vs their current (fabricated DW) values. Review artifact for Steve before any live backfill.
import { readFileSync, writeFileSync } from 'node:fs';
import { gql } from './shopify.mjs';
const recovered = JSON.parse(readFileSync('deep-recovered.json', 'utf8'));
const byMfr = recovered.filter(r => r.via === 'mfr_number');
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function q(query, v) { for (let a = 0; ; a++) { try { return (await gql(query, v)).data; } catch (e) { if (/THROTTLED|<html/i.test(e.message) && a < 8) { await sleep(2000 * (a + 1)); continue; } throw e; } } }
const plan = [];
for (let i = 0; i < byMfr.length; i += 50) {
const ids = byMfr.slice(i, i + 50);
const idMap = Object.fromEntries(ids.map(r => [r.id, r.momentum_number]));
const d = await q(`query{ nodes(ids:[${ids.map(r => `"${r.id}"`).join(',')}]){ ... on Product{ id handle
g:metafield(namespace:"global",key:"dw_sku"){value} c:metafield(namespace:"custom",key:"dw_sku"){value}
variants(first:6){ nodes{ id sku selectedOptions{ name value } } } } } }`);
for (const p of d.nodes || []) {
if (!p) continue;
const number = idMap[p.id];
const varChanges = p.variants.nodes.map(v => {
// suffix from the variant's OWN current sku first (authoritative), then option label
const cur = (v.sku || '').toLowerCase();
const opt = (v.selectedOptions.find(o => /purchase/i.test(o.name))?.value || '').toLowerCase();
let suf;
if (/-sample$/.test(cur) || /sample/.test(opt)) suf = 'sample';
else if (/-roll$/.test(cur) || /roll/.test(opt)) suf = 'roll';
else suf = 'yard';
const to = `${number}-${suf}`;
return v.sku !== to ? { from: v.sku, to, suf } : null;
}).filter(Boolean);
// collision guard: two variants must not map to the same target
const targets = varChanges.map(c => c.to);
if (new Set(targets).size !== targets.length) plan.push({ handle: p.handle, number, COLLISION: targets });
plan.push({ handle: p.handle, number, dw_sku_from: p.g?.value || p.c?.value, dw_sku_to: number, varChanges });
}
if (i % 200 === 0) process.stderr.write(` ...${i}/${byMfr.length}\n`);
}
writeFileSync('generator-backfill-plan.json', JSON.stringify(plan, null, 0));
console.log(JSON.stringify({
feed_matched_products: byMfr.length, in_plan: plan.length,
dw_sku_changes: plan.filter(p => p.dw_sku_from !== p.dw_sku_to).length,
variant_sku_changes: plan.reduce((a, p) => a + p.varChanges.length, 0),
}, null, 2));
console.log('sample:', plan.slice(0, 4).map(p => `${p.handle}: dw_sku ${p.dw_sku_from}->${p.dw_sku_to}, vars ${JSON.stringify(p.varChanges.map(c => c.from + '->' + c.to))}`).join('\n '));