[object Object]

← back to Gmc 425 Supplemental Feed

supplemental-feed dry-run generator + primary-feed spike: ROOT PROVEN — Shopify Google channel submits the position-1 variant; 22/22 hard leakers have Sample@pos1; v0.1.2

05b55a3485396570753c1da965a61628932a8475 · 2026-08-10 09:17:10 -0700 · Steve Abrams

Files touched

Diff

commit 05b55a3485396570753c1da965a61628932a8475
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 10 09:17:10 2026 -0700

    supplemental-feed dry-run generator + primary-feed spike: ROOT PROVEN — Shopify Google channel submits the position-1 variant; 22/22 hard leakers have Sample@pos1; v0.1.2
---
 build-supplemental-feed.mjs | 53 +++++++++++++++++++++++++++++++++++++++++++++
 package.json                |  2 +-
 spike-primary-feed.mjs      | 31 ++++++++++++++++++++++++++
 3 files changed, 85 insertions(+), 1 deletion(-)

diff --git a/build-supplemental-feed.mjs b/build-supplemental-feed.mjs
new file mode 100644
index 0000000..86863d6
--- /dev/null
+++ b/build-supplemental-feed.mjs
@@ -0,0 +1,53 @@
+#!/usr/bin/env node
+/**
+ * build-supplemental-feed.mjs — DRY-RUN generator. Emits a Google supplemental feed that
+ * suppresses the $4.25 Sample offer (excluded_destination: Shopping_ads) for every
+ * SUPP_FIXABLE product, so only the real Roll offer is advertised. Keeps Google presence.
+ *
+ * Reads a classification json (default out/sample-classification.json). Writes:
+ *   out/supplemental-feed-suppress-sample.tsv   (the feed FILE — inspect before upload)
+ *   out/supplemental-feed-registration.json     (datafeeds.insert payload — NOT posted)
+ *   out/supplemental-feed-manifest.json         (what/why/reversibility)
+ * NEVER touches Merchant Center. Upload/register is a separate Steve-gated step.
+ *
+ * Offer-id note: Shopify primary feed uses offer id `shopify_US_<pid>_<vid>` within feed
+ * target online:en:US. We emit the bare offer id (feed `id` column) AND the full REST id for cross-check.
+ */
+import fs from 'node:fs';
+const OUT=new URL('./out/',import.meta.url); fs.mkdirSync(OUT,{recursive:true});
+const src=process.argv[2]||new URL('sample-classification.json',OUT);
+const data=JSON.parse(fs.readFileSync(src,'utf8'));
+const rows=(data.rows||data).filter(r=>r.classification==='SUPP_FIXABLE');
+// Feed: suppress the $4.25 SAMPLE offer from Shopping ads → Roll becomes the advertised offer.
+const header='id\texcluded_destination';
+const lines=rows.map(r=>`shopify_US_${r.pid}_${r.sampleVid}\tShopping_ads`);
+const tsv=[header,...lines].join('\n')+'\n';
+fs.writeFileSync(new URL('supplemental-feed-suppress-sample.tsv',OUT),tsv);
+// Registration payload (datafeeds.insert) — supplemental, target online/en/US. NOT posted.
+const reg={
+  name:'dw-425-suppress-sample-supplemental',
+  contentType:'products',
+  attributeLanguage:'en',
+  fileName:'supplemental-feed-suppress-sample.tsv',
+  format:{fileEncoding:'utf-8',columnDelimiter:'TAB'},
+  targets:[{country:'US',language:'en',includedDestinations:[],excludedDestinations:[]}],
+  _note:'SUPPLEMENTAL feed. Link to the primary Shopify feed. Overlays excluded_destination=Shopping_ads onto the $4.25 sample offers so only the Roll offer advertises. REVERSIBLE: delete this supplemental feed / remove the rows to restore.',
+};
+fs.writeFileSync(new URL('supplemental-feed-registration.json',OUT),JSON.stringify(reg,null,2));
+const manifest={
+  generated:'DRY-RUN (not uploaded)',
+  class:'SUPP_FIXABLE',
+  offers_suppressed:rows.length,
+  action:'excluded_destination=Shopping_ads on each $4.25 Sample offer (Roll offer left intact → becomes advertised price)',
+  reversibility:'Delete the supplemental feed OR remove rows → sample offers return. No product/variant/price is mutated.',
+  full_population_path:'Run sample-and-classify.mjs with N=all (bounded by time) to classify the whole leaks population, then re-run this generator on the full classification to cover all ~18,500 SUPP_FIXABLE.',
+  sample_rows:rows.slice(0,5).map(r=>({pid:r.pid,sampleVid:r.sampleVid,rollVid:r.rollVid,offerId:`shopify_US_${r.pid}_${r.sampleVid}`})),
+};
+fs.writeFileSync(new URL('supplemental-feed-manifest.json',OUT),JSON.stringify(manifest,null,2));
+console.log(`SUPP_FIXABLE rows in source: ${rows.length}`);
+console.log(`Wrote (DRY-RUN, not uploaded):`);
+console.log(`  out/supplemental-feed-suppress-sample.tsv   (${rows.length} suppress rows)`);
+console.log(`  out/supplemental-feed-registration.json     (datafeeds.insert payload — NOT posted)`);
+console.log(`  out/supplemental-feed-manifest.json`);
+console.log(`\nFeed preview (first 4 lines):`);
+console.log(tsv.split('\n').slice(0,4).join('\n'));
diff --git a/package.json b/package.json
index f113b34..934134d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "gmc-425-supplemental-feed",
-  "version": "0.1.1",
+  "version": "0.1.2",
   "private": true,
   "type": "module",
   "description": "DRY-RUN classifier for the DW $4.25 GMC leak: determines per-offer whether a supplemental feed can fix it, or whether the Roll is absent from the primary feed (needs a Shopify feed fix / unpublish). Never uploads.",
diff --git a/spike-primary-feed.mjs b/spike-primary-feed.mjs
new file mode 100644
index 0000000..83af3aa
--- /dev/null
+++ b/spike-primary-feed.mjs
@@ -0,0 +1,31 @@
+#!/usr/bin/env node
+/** spike-primary-feed.mjs — READ-ONLY diagnosis of SUPP_CANT_HELP products (Roll absent from feed).
+ * For each, pull live Shopify variant order/availability to find WHY Google submitted only the Sample. */
+import fs from 'node:fs';
+const HOME=process.env.HOME, SHOP='designer-laboratory-sandbox';
+const API=`https://${SHOP}.myshopify.com/admin/api/2024-10/graphql.json`;
+const T=(fs.readFileSync(HOME+'/Projects/secrets-manager/.env','utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1].replace(/['"]/g,'').trim();
+async function gql(q,v){const r=await fetch(API,{method:'POST',headers:{'X-Shopify-Access-Token':T,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v||{}})});return r.json();}
+const rows=JSON.parse(fs.readFileSync(new URL('./out/sample-classification.json',import.meta.url),'utf8')).rows.filter(r=>r.classification==='SUPP_CANT_HELP');
+const Q=`query($id:ID!){product(id:$id){title
+  variants(first:20){nodes{id title position price availableForSale inventoryQuantity barcode}}}}`;
+let samplePos1=0, rollUnavail=0, rollNoInv=0, sampleHasBarcode=0, rollHasBarcode=0, n=0;
+const detail=[];
+for(const r of rows){
+  const j=await gql(Q,{id:`gid://shopify/Product/${r.pid}`}); const p=j.data?.product; if(!p)continue; n++;
+  const vs=p.variants.nodes.map(v=>({...v,vid:v.id.split('/').pop(),pr:parseFloat(v.price)}));
+  const sample=vs.find(v=>v.vid===r.sampleVid)||vs.find(v=>v.pr<=4.25);
+  const roll=vs.find(v=>v.vid===r.rollVid)||vs.reduce((a,b)=>b.pr>a.pr?b:a,vs[0]);
+  if(sample?.position===1) samplePos1++;
+  if(roll && !roll.availableForSale) rollUnavail++;
+  if(roll && (roll.inventoryQuantity||0)<=0) rollNoInv++;
+  if(sample?.barcode) sampleHasBarcode++;
+  if(roll?.barcode) rollHasBarcode++;
+  if(detail.length<8) detail.push({title:p.title.slice(0,34),samplePos:sample?.position,rollPos:roll?.position,rollAvail:roll?.availableForSale,rollInv:roll?.inventoryQuantity,sampleBC:!!sample?.barcode,rollBC:!!roll?.barcode});
+}
+console.log(`SUPP_CANT_HELP analyzed: ${n}`);
+console.log(`  sample is variant position 1 (default/first): ${samplePos1}/${n}  (${(100*samplePos1/n).toFixed(0)}%)`);
+console.log(`  roll NOT availableForSale:                     ${rollUnavail}/${n}`);
+console.log(`  roll inventoryQuantity <= 0:                   ${rollNoInv}/${n}`);
+console.log(`  sample has barcode/GTIN: ${sampleHasBarcode}/${n} · roll has barcode/GTIN: ${rollHasBarcode}/${n}`);
+console.log(`\nsample detail:`); detail.forEach(d=>console.log('  ',JSON.stringify(d)));

← 60c265d add bounded leak sampler/classifier (N=150): true leak rate  ·  back to Gmc 425 Supplemental Feed  ·  add full-population classifier (MC-feed-set based, bounded) 92213f9 →