[object Object]

← back to Gmc Titlefix

step-e: DTD-B verify-and-retry — page processed feed, re-push only un-flipped offers

85cc622b159c1299e1de4ff3c18576fe203bb608 · 2026-06-18 16:27:29 -0700 · SteveStudio2

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files touched

Diff

commit 85cc622b159c1299e1de4ff3c18576fe203bb608
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Thu Jun 18 16:27:29 2026 -0700

    step-e: DTD-B verify-and-retry — page processed feed, re-push only un-flipped offers
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
 step-e-verify-retry.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)

diff --git a/step-e-verify-retry.js b/step-e-verify-retry.js
new file mode 100644
index 0000000..f9e0cfe
--- /dev/null
+++ b/step-e-verify-retry.js
@@ -0,0 +1,69 @@
+// DTD-B (2026-06-18, 3/3): page the live processed feed, find which of our 29,672
+// offers did NOT flip to a "Sample:" title (transient-500 fails + reprocess-lag),
+// and re-push ONLY those to the canonical supplemental DS. Doubles as the verify pass.
+//
+// READ phase is read-only (products.list paging). WRITE phase re-pushes only the
+// residual un-flipped offers (same body shape as step-d) — within the approved
+// "consolidate + scale all" scope. Idempotent.
+//
+// Usage: node step-e-verify-retry.js [dataSourceName]   (default DS = 10677010255)
+const {token,MERCHANT}=require('./_auth');
+const fs=require('fs');
+const DS=process.argv[2]||'accounts/146735262/dataSources/10677010255';
+const list=require('./gmc_titlefix_list.json');
+const want=new Map();                 // offerId -> proposedTitle (must start "Sample:")
+for(const r of list) want.set(r.offerId, r.proposedTitle);
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+
+(async()=>{
+  let tok=await token(), tokAt=Date.now();
+  const reTok=async()=>{ if(Date.now()-tokAt>50*60*1000){ tok=await token(); tokAt=Date.now(); } };
+
+  // ---- READ: page the whole processed feed, record effective title for our offers ----
+  const seen=new Map();                // offerId -> effective title (for offers in `want`)
+  let pageToken='', pages=0, scanned=0;
+  do{
+    await reTok();
+    const u=new URL(`https://shoppingcontent.googleapis.com/content/v2.1/${MERCHANT}/products`);
+    u.searchParams.set('maxResults','250');
+    if(pageToken) u.searchParams.set('pageToken',pageToken);
+    const r=await fetch(u,{headers:{Authorization:'Bearer '+tok}});
+    if(r.status===429||r.status>=500){ await sleep(2500); continue; }
+    const j=await r.json();
+    for(const p of (j.resources||[])){
+      scanned++;
+      const off=p.offerId;
+      if(want.has(off)) seen.set(off, p.title||'');
+    }
+    pageToken=j.nextPageToken||'';
+    if(++pages%25===0) console.log(`  paged ${pages} (scanned ${scanned}, matched ${seen.size}/${want.size})`);
+  }while(pageToken);
+  console.log(`READ done: ${pages} pages, scanned ${scanned}, matched ${seen.size}/${want.size} of our offers`);
+
+  // ---- DIFF: which of our offers are NOT yet "Sample:"-prefixed (or absent from feed) ----
+  const residual=[];
+  for(const r of list){
+    const t=seen.get(r.offerId);
+    if(t===undefined || !/^Sample:/.test(t)) residual.push(r);
+  }
+  const absent=residual.filter(r=>!seen.has(r.offerId)).length;
+  console.log(`DIFF: ${residual.length} offers still un-flipped (${absent} absent from feed, ${residual.length-absent} present-but-not-Sample)`);
+  fs.writeFileSync('/tmp/gmc-titlefix-residual.json', JSON.stringify(residual.map(r=>r.offerId),null,2));
+
+  if(!residual.length){ console.log('✅ ALL 29,672 flipped — nothing to retry.'); return; }
+
+  // ---- WRITE: re-push only the residual (with one 500-retry each) ----
+  let ok=0, fail=0;
+  for(let i=0;i<residual.length;i++){
+    await reTok();
+    const row=residual[i];
+    const url=`https://merchantapi.googleapis.com/products/v1/accounts/${MERCHANT}/productInputs:insert?dataSource=${encodeURIComponent(DS)}`;
+    const body={ offerId:row.offerId, contentLanguage:'en', feedLabel:'US', productAttributes:{ title: row.proposedTitle } };
+    let r=await fetch(url,{method:'POST',headers:{Authorization:'Bearer '+tok,'Content-Type':'application/json'},body:JSON.stringify(body)});
+    if(r.status===429||r.status>=500){ await sleep(3000); r=await fetch(url,{method:'POST',headers:{Authorization:'Bearer '+tok,'Content-Type':'application/json'},body:JSON.stringify(body)}); }
+    if(r.ok) ok++; else { fail++; if(fail<=15) console.error('RETRY-FAIL',row.offerId,r.status,(await r.text()).slice(0,100)); }
+    if(i%200===0) console.log(`  retry ${i}/${residual.length} | ok ${ok} fail ${fail}`);
+  }
+  console.log(`RETRY DONE: re-pushed ${residual.length} | ok ${ok} fail ${fail}`);
+  console.log('Re-run this script after a few min to confirm residual → 0 (allow reprocess lag).');
+})();

← 78f9b56 step-2: full-feed verifier + wait-for-scaleout-then-verify w  ·  back to Gmc Titlefix  ·  step-f: bounded verify-gated retry (dry-run default, CONFIRM 084653f →