← back to Gmc 425 Supplemental Feed
lever1b: add read-only GMC verification (CA/GB currency-mismatch drop) + v0.2.0 (session close)
76439421a8b2ec99ab8a3faa7e45af412b28d444 · 2026-08-10 11:45:53 -0700 · Steve Abrams
Files touched
A lever1b-verify.mjsM package.json
Diff
commit 76439421a8b2ec99ab8a3faa7e45af412b28d444
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 10 11:45:53 2026 -0700
lever1b: add read-only GMC verification (CA/GB currency-mismatch drop) + v0.2.0 (session close)
---
lever1b-verify.mjs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
package.json | 2 +-
2 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/lever1b-verify.mjs b/lever1b-verify.mjs
new file mode 100644
index 0000000..51b70bc
--- /dev/null
+++ b/lever1b-verify.mjs
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+/** lever1b-verify.mjs — READ-ONLY GMC verification for Lever-1B (CA/GB local-currency markets).
+ * Samples productstatuses, counts CA/GB disapproving item-level issues broken down by code.
+ * Mode: `baseline` writes out/lever1b-baseline.json ; `recheck` compares to baseline.
+ * No markets scope needed — pure Content API v2.1 read. */
+import fs from 'node:fs'; import crypto from 'node:crypto';
+const HOME=process.env.HOME, MERCHANT='146735262';
+const SA_PATH=HOME+'/Projects/secrets-manager/gmc-sa-146735262.json';
+const MODE=process.argv[2]||'baseline';
+const MAX_PAGES=parseInt(process.argv[3]||'12',10); // 12*250 = up to 3,000 offers sampled
+const OUT=new URL('./out/lever1b-baseline.json',import.meta.url);
+const b64=b=>Buffer.from(b).toString('base64').replace(/=/g,'').replace(/\+/g,'-').replace(/\//g,'_');
+async function mcToken(){const SA=JSON.parse(fs.readFileSync(SA_PATH,'utf8'));const now=Math.floor(Date.now()/1000);
+ const si=b64(JSON.stringify({alg:'RS256',typ:'JWT'}))+'.'+b64(JSON.stringify({iss:SA.client_email,scope:'https://www.googleapis.com/auth/content',aud:'https://oauth2.googleapis.com/token',iat:now,exp:now+3600}));
+ const s=crypto.createSign('RSA-SHA256');s.update(si);const jwt=si+'.'+b64(s.sign(SA.private_key));
+ const r=await fetch('https://oauth2.googleapis.com/token',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({grant_type:'urn:ietf:params:oauth:grant-type:jwt-bearer',assertion:jwt})});
+ return (await r.json()).access_token;}
+const tok=await mcToken(); const H={Authorization:`Bearer ${tok}`};
+const TARGET=new Set(['CA','GB']);
+const tally={ sampled:0, CA:{disapproved:0,byCode:{}}, GB:{disapproved:0,byCode:{}}, currencyOffers:new Set() };
+let pageToken=null, pages=0;
+do{
+ const u=new URL(`https://shoppingcontent.googleapis.com/content/v2.1/${MERCHANT}/productstatuses`);
+ u.searchParams.set('maxResults','250'); if(pageToken)u.searchParams.set('pageToken',pageToken);
+ const r=await fetch(u,{headers:H}); const j=await r.json();
+ if(j.error){console.error('API error',j.error.message);break;}
+ for(const p of (j.resources||[])){
+ tally.sampled++;
+ for(const iss of (p.itemLevelIssues||[])){
+ const ac=iss.applicableCountries||[];
+ for(const c of ['CA','GB']){
+ if(ac.includes(c) && iss.servability==='disapproved'){
+ tally[c].disapproved++;
+ tally[c].byCode[iss.code]=(tally[c].byCode[iss.code]||0)+1;
+ if(/curren/i.test(iss.code)||/curren/i.test(iss.detail||'')) tally.currencyOffers.add(p.productId);
+ }
+ }
+ }
+ }
+ pageToken=j.nextPageToken; pages++;
+}while(pageToken && pages<MAX_PAGES);
+
+const snap={ mode:MODE, sampled:tally.sampled, pages,
+ CA:tally.CA, GB:tally.GB, currencyMismatchOffers:tally.currencyOffers.size };
+console.log(JSON.stringify(snap,null,2));
+
+if(MODE==='baseline'){
+ fs.mkdirSync(new URL('./out/',import.meta.url),{recursive:true});
+ fs.writeFileSync(OUT,JSON.stringify(snap,null,2));
+ console.log(`\nBASELINE saved (${snap.sampled} offers sampled): CA disapproved=${snap.CA.disapproved}, GB disapproved=${snap.GB.disapproved}, currency-mismatch offers=${snap.currencyMismatchOffers}. Re-run \`node lever1b-verify.mjs recheck\` after Google re-sync.`);
+}else{
+ let base=null; try{base=JSON.parse(fs.readFileSync(OUT,'utf8'));}catch{}
+ if(!base){console.log('\n(no baseline on disk — this run IS the reference)');}
+ else{
+ const dCA=base.CA.disapproved-snap.CA.disapproved, dGB=base.GB.disapproved-snap.GB.disapproved, dCur=base.currencyMismatchOffers-snap.currencyMismatchOffers;
+ console.log(`\nRECHECK vs baseline (sample ${base.sampled}->${snap.sampled}): CA disapproved ${base.CA.disapproved}->${snap.CA.disapproved} (${dCA>=0?'-':'+'}${Math.abs(dCA)}), GB ${base.GB.disapproved}->${snap.GB.disapproved} (${dGB>=0?'-':'+'}${Math.abs(dGB)}), currency-mismatch offers ${base.currencyMismatchOffers}->${snap.currencyMismatchOffers} (${dCur>=0?'-':'+'}${Math.abs(dCur)}).`);
+ console.log(dCur>0 ? 'VERDICT: currency-mismatch disapprovals DROPPING -> Lever-1B markets working.' : 'VERDICT: no drop yet -> Google may not have re-synced, or markets not active/feed not targeting CA+GB.');
+ }
+}
diff --git a/package.json b/package.json
index 3440a43..897f97e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gmc-425-supplemental-feed",
- "version": "0.1.3",
+ "version": "0.2.0",
"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.",
← d734ca3 spike: Merchant API v1 works but 7 'DW Real-Price Overrides'
·
back to Gmc 425 Supplemental Feed
·
add republish-channel-canary.mjs: SUPP_CANT_HELP fix tool (d 28cfbfc →