← 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'));