← 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 →