← back to Designerwallcoverings
TK-10238/10210: verify no_roll_variant_price exclusions — 0 false positives across all 28545 (fix 0b4b249 already recovered 17534 into feed)
666e9369fde2d553288a823c7d163ae3d01cc748 · 2026-08-05 07:49:09 -0700 · Steve Abrams
Files touched
A data/google-feed/no-roll-falsepos.jsonA scripts/google-feed/verify-no-roll-falsepos.mjs
Diff
commit 666e9369fde2d553288a823c7d163ae3d01cc748
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Aug 5 07:49:09 2026 -0700
TK-10238/10210: verify no_roll_variant_price exclusions — 0 false positives across all 28545 (fix 0b4b249 already recovered 17534 into feed)
---
data/google-feed/no-roll-falsepos.json | 1 +
scripts/google-feed/verify-no-roll-falsepos.mjs | 65 +++++++++++++++++++++++++
2 files changed, 66 insertions(+)
diff --git a/data/google-feed/no-roll-falsepos.json b/data/google-feed/no-roll-falsepos.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/data/google-feed/no-roll-falsepos.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/scripts/google-feed/verify-no-roll-falsepos.mjs b/scripts/google-feed/verify-no-roll-falsepos.mjs
new file mode 100644
index 0000000..34d10a9
--- /dev/null
+++ b/scripts/google-feed/verify-no-roll-falsepos.mjs
@@ -0,0 +1,65 @@
+#!/usr/bin/env node
+/**
+ * verify-no-roll-falsepos.mjs — READ-ONLY.
+ * For every product excluded with reason exactly ['no_roll_variant_price'],
+ * re-query LIVE Shopify variants and apply the SAME classifier the feed uses.
+ * A TRUE FALSE POSITIVE = a live product that, under the current fixed logic,
+ * actually HAS a non-sample variant with a finite price >= ROLL_PRICE_MIN.
+ * Emits: data/google-feed/no-roll-falsepos.json (the recoverable set)
+ * data/google-feed/no-roll-confirmed.json (genuinely sample-only, count only)
+ */
+import fs from 'node:fs';
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const VER = '2024-10';
+const envTxt = fs.readFileSync(process.env.HOME + '/Projects/secrets-manager/.env', 'utf8');
+const TOKEN = (envTxt.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1]?.trim();
+const URL = `https://${SHOP}/admin/api/${VER}/graphql.json`;
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+const ROLL_PRICE_MIN = 5;
+const looksSample = v => /(sample|memo|swatch)/i.test([v.title, v.sku].join(' ')) || /-sample$/i.test(v.sku || '');
+const isSample = v => looksSample(v) && !(parseFloat(v.price) > ROLL_PRICE_MIN);
+
+async function gql(query, variables) {
+ for (let a = 0; a < 8; a++) {
+ let res, j;
+ try {
+ res = await fetch(URL, { method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables }) });
+ j = await res.json();
+ } catch (e) { await sleep(1500 * (a + 1)); continue; }
+ if (j.errors) { if (JSON.stringify(j.errors).includes('THROTTLED')) { await sleep(2000 * (a + 1)); continue; } throw new Error(JSON.stringify(j.errors)); }
+ const t = j.extensions?.cost?.throttleStatus;
+ if (t && t.currentlyAvailable < 500) await sleep(1000);
+ return j.data;
+ }
+ throw new Error('exhausted retries');
+}
+
+const NODES_Q = `query($ids:[ID!]!){ nodes(ids:$ids){ ... on Product { id title vendor status variants(first:40){ nodes{ title sku price } } } } }`;
+
+const ids = fs.readFileSync('/tmp/all_nr_ids.txt', 'utf8').trim().split('\n').map(s => `gid://shopify/Product/${s.trim()}`);
+const falsePos = [];
+let confirmed = 0, deleted = 0, notActive = 0, scanned = 0;
+
+for (let i = 0; i < ids.length; i += 50) {
+ const batch = ids.slice(i, i + 50);
+ const data = await gql(NODES_Q, { ids: batch });
+ for (const p of data.nodes) {
+ scanned++;
+ if (!p) { deleted++; continue; }
+ if (p.status !== 'ACTIVE') { notActive++; continue; }
+ const vs = p.variants?.nodes || [];
+ const rolls = vs.filter(v => !isSample(v)).map(v => parseFloat(v.price)).filter(Number.isFinite);
+ const rollPrice = rolls.length ? Math.max(...rolls) : null;
+ if (rollPrice != null && rollPrice >= ROLL_PRICE_MIN) {
+ falsePos.push({ id: p.id.split('/').pop(), vendor: p.vendor, title: p.title, rollPrice,
+ variants: vs.map(v => ({ t: v.title, sku: v.sku, price: v.price, sample: isSample(v) })) });
+ } else {
+ confirmed++;
+ }
+ }
+ if (i % 2000 === 0) process.stderr.write(` scanned ${scanned} | falsePos ${falsePos.length} | confirmed ${confirmed} | deleted ${deleted} | notActive ${notActive}\n`);
+}
+
+fs.writeFileSync('data/google-feed/no-roll-falsepos.json', JSON.stringify(falsePos, null, 2));
+console.log(JSON.stringify({ scanned, falsePos: falsePos.length, confirmed_sample_only: confirmed, deleted, notActive }, null, 2));
← 6b7ca7c auto-save: 2026-08-05T07:40:04 (2 files) — data/orphan-clean
·
back to Designerwallcoverings
·
TK-10014: live DWKK price vs authoritative MAP comparator — 3f5ba6e →