[object Object]

← back to Gmc Titlefix

add gmc price-override + shipping-weight-override generators (feed-only, checkout untouched; gated to run)

0cd0040c8b12e4dafe8f4d917ed94fa521b9f809 · 2026-07-09 08:43:12 -0700 · steve

Files touched

Diff

commit 0cd0040c8b12e4dafe8f4d917ed94fa521b9f809
Author: steve <steve@designerwallcoverings.com>
Date:   Thu Jul 9 08:43:12 2026 -0700

    add gmc price-override + shipping-weight-override generators (feed-only, checkout untouched; gated to run)
---
 gmc-weight-override.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)

diff --git a/gmc-weight-override.js b/gmc-weight-override.js
new file mode 100644
index 0000000..2e0e74f
--- /dev/null
+++ b/gmc-weight-override.js
@@ -0,0 +1,81 @@
+// GMC SHIPPING-WEIGHT OVERRIDE — set shipping_weight = 1 lb on Google offers that are
+// disapproved for missing_shipping_weight. FEED-ONLY: this is a Google Merchant feed
+// attribute, entirely separate from the Shopify variant weight that drives real checkout.
+// Setting it here does NOT change what a customer pays at checkout (Shopify owns that).
+//
+// Targets come from the gmc-viewer cache (data/catalog.json) — offers whose issues[]
+// include 'missing_shipping_weight'. Optionally also arm $4.25 sample offers preemptively.
+//
+//   node gmc-weight-override.js                 # DRY-RUN: build target list from cache, no writes
+//   node gmc-weight-override.js --include-samples# also arm every $4.25 sample offer
+//   node gmc-weight-override.js --create-ds       # GATED: create the supplemental datasource
+//   node gmc-weight-override.js --apply <DS> [--canary N]  # GATED: push weight overrides
+//
+// Cost: $0 (Merchant API + local cache read; no per-call charge).
+const { token, MERCHANT } = require('./_auth.js');
+const fs = require('fs');
+const CACHE = '/Users/macstudio3/Projects/gmc-viewer/data/catalog.json';
+const OUT = '/Users/macstudio3/.claude/yolo-queue/gmc-weight-override-list.json';
+const WEIGHT_LB = parseFloat(process.env.WEIGHT_LB || '1');
+const args = process.argv.slice(2);
+const INCLUDE_SAMPLES = args.includes('--include-samples');
+const CREATE_DS = args.includes('--create-ds');
+const APPLY = args.includes('--apply');
+const DS = APPLY ? args[args.indexOf('--apply') + 1] : null;
+const CANARY = args.includes('--canary') ? parseInt(args[args.indexOf('--canary') + 1] || '10', 10) : 0;
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+function buildList() {
+  const cat = JSON.parse(fs.readFileSync(CACHE, 'utf8'));
+  const targets = [];
+  for (const i of cat.items) {
+    const needsWeight = (i.issues || []).includes('missing_shipping_weight');
+    const isSample = Math.abs(i.price - 4.25) < 0.01;
+    if (needsWeight || (INCLUDE_SAMPLES && isSample)) {
+      targets.push({ offerId: i.offerId, contentLanguage: (i.id || '').split(':')[1] || 'en', feedLabel: i.country || 'US', reason: needsWeight ? 'missing_shipping_weight' : 'sample-preemptive', title: i.title });
+    }
+  }
+  return { generated_at: new Date().toISOString(), weight_lb: WEIGHT_LB, include_samples: INCLUDE_SAMPLES, count: targets.length, targets };
+}
+
+async function createDataSource() {
+  const tok = await token();
+  const url = `https://merchantapi.googleapis.com/datasources/v1/accounts/${MERCHANT}/dataSources`;
+  for (const supp of [{}, { contentLanguage: 'en' }, { contentLanguage: 'en', feedLabel: 'US' }]) {
+    const r = await fetch(url, { method: 'POST', headers: { Authorization: 'Bearer ' + tok, 'Content-Type': 'application/json' }, body: JSON.stringify({ displayName: 'DW Shipping Weight Overrides', supplementalProductDataSource: supp }) });
+    const j = await r.json();
+    if (r.status >= 200 && r.status < 300 && j.name) { console.log('DATASOURCE CREATED:\n' + j.name); return; }
+    console.error('  shape', JSON.stringify(supp), '->', r.status, (j.error?.message || '').slice(0, 120));
+  }
+  console.error('all datasource shapes failed');
+}
+
+async function apply(ds) {
+  const list = JSON.parse(fs.readFileSync(OUT, 'utf8')).targets;
+  const slice = CANARY ? list.slice(0, CANARY) : list;
+  let tok = await token(), tokAt = Date.now(), ok = 0, fail = 0;
+  console.log(`Pushing shipping_weight=${WEIGHT_LB}lb for ${slice.length} offers → ${ds}`);
+  for (let i = 0; i < slice.length; i++) {
+    if (Date.now() - tokAt > 50 * 60 * 1000) { tok = await token(); tokAt = Date.now(); }
+    const row = slice[i];
+    const url = `https://merchantapi.googleapis.com/products/v1/accounts/${MERCHANT}/productInputs:insert?dataSource=${encodeURIComponent(ds)}`;
+    const body = { offerId: row.offerId, contentLanguage: row.contentLanguage, feedLabel: row.feedLabel, productAttributes: { shipping: [], shippingWeight: { value: WEIGHT_LB, unit: 'lb' } } };
+    const r = await fetch(url, { method: 'POST', headers: { Authorization: 'Bearer ' + tok, 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
+    if (r.ok) ok++; else { fail++; if (fail <= 10) console.error('FAIL', row.offerId, r.status, (await r.text()).slice(0, 160)); }
+    if (i % 500 === 0) console.log(`  ${i}/${slice.length} | ok ${ok} fail ${fail}`);
+    if (r.status === 429) await sleep(3000);
+  }
+  console.log(`DONE: ok ${ok} fail ${fail} of ${slice.length}`);
+}
+
+(async () => {
+  if (CREATE_DS) return createDataSource();
+  if (APPLY) { if (!DS) { console.error('need datasource: --apply <DS>'); process.exit(1); } return apply(DS); }
+  const out = buildList();
+  fs.writeFileSync(OUT, JSON.stringify(out, null, 2));
+  const byReason = out.targets.reduce((a, t) => (a[t.reason] = (a[t.reason] || 0) + 1, a), {});
+  console.log(`Shipping-weight override targets: ${out.count} @ ${WEIGHT_LB}lb (feed-only, checkout unaffected)`);
+  console.log('  by reason:', JSON.stringify(byReason));
+  console.log(`Wrote ${OUT}`);
+  out.targets.slice(0, 6).forEach(t => console.log(`  ${t.offerId}  ${t.reason}  ${(t.title || '').slice(0, 40)}`));
+})().catch(e => { console.error('FATAL', e.message); process.exit(1); });

← 282cc63 auto-save: 2026-07-09T08:39:40 (1 files) — _diag425-tmp.js  ·  back to Gmc Titlefix  ·  weight override: tiered feed weights — real 1lb / samples 0. e01bb10 →