← back to Gmc 425 Supplemental Feed

build-supplemental-feed.mjs

54 lines

#!/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'));