[object Object]

← 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

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 →