← back to Designerwallcoverings
night-pdp-verify: unknown expected price -> NOT-MEASURED, keep catch error reason
398290ef852b5e2604dfeeab8ebe2ca768b7f161 · 2026-09-24 15:01:03 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LfkajczsrHUsBBKvhRwcCG
Files touched
A scripts/sanderson-onboard/night-pdp-verify.mjs
Diff
commit 398290ef852b5e2604dfeeab8ebe2ca768b7f161
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 24 15:01:03 2026 -0700
night-pdp-verify: unknown expected price -> NOT-MEASURED, keep catch error reason
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LfkajczsrHUsBBKvhRwcCG
---
scripts/sanderson-onboard/night-pdp-verify.mjs | 76 ++++++++++++++++++++++++++
1 file changed, 76 insertions(+)
diff --git a/scripts/sanderson-onboard/night-pdp-verify.mjs b/scripts/sanderson-onboard/night-pdp-verify.mjs
new file mode 100644
index 0000000..32497a8
--- /dev/null
+++ b/scripts/sanderson-onboard/night-pdp-verify.mjs
@@ -0,0 +1,76 @@
+// TK-11046 night refill (cron 5071d4c2, 2026-09-18) — READ-ONLY storefront PDP sample.
+// 30 rows from out/night-sample-20260918.json (the 2 Dalmatians first + 28 spread across 502).
+// Paced ~1 req/s, retries on 429/5xx with backoff, honest NOT-MEASURED on exhaustion.
+// No writes anywhere except one local JSON report.
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dir = path.dirname(fileURLToPath(import.meta.url));
+const outDir = path.join(__dir, 'out');
+const PUBLIC = 'designerwallcoverings.com';
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+const SAMPLE_PRICE = 4.25;
+
+const sample = JSON.parse(fs.readFileSync(path.join(outDir, 'night-sample-20260918.json'), 'utf8'));
+// expected sellable price per sku from the approved payloads
+const prices = {};
+for (const line of fs.readFileSync(path.join(outDir, 'payloads-imgfixed.jsonl'), 'utf8').split('\n')) {
+ if (!line.trim()) continue;
+ const d = JSON.parse(line);
+ prices[d.sku] = Number(d.retail);
+}
+
+const rows = [];
+for (const p of sample) {
+ const url = `https://${PUBLIC}/products/${p.handle}.js`;
+ let res = { sku: p.sku, handle: p.handle, url, state: 'NOT-MEASURED', http: null, reasons: [], detail: null };
+ for (let a = 0; a < 4; a++) {
+ try {
+ const r = await fetch(url, { headers: { 'User-Agent': 'DW-internal-pdp-verify/1.0' } });
+ res.http = r.status;
+ if (r.status === 429 || r.status >= 500) { await sleep(1500 * (a + 1)); continue; }
+ if (r.status === 404) { res.state = 'MEASURED-BAD'; res.reasons.push('storefront 404'); break; }
+ if (!r.ok) { res.reasons.push(`http ${r.status}`); break; }
+ const j = await r.json();
+ const pr = j.product || j;
+ const bad = [];
+ const unmeasured = [];
+ if (!(pr.images || []).length) bad.push('no image');
+ const vs = pr.variants || [];
+ const sellable = vs.filter(v => !/sample/i.test(v.title || ''));
+ if (!sellable.length) bad.push('no sellable variant');
+ else {
+ // storefront .js returns prices in CENTS — compare in cents
+ if (sellable.some(v => Number(v.price) === SAMPLE_PRICE * 100)) bad.push('$4.25 leaking as sellable price');
+ if (sellable.some(v => !(Number(v.price) > 0))) bad.push('price <= 0');
+ const exp = prices[p.sku];
+ // an unknown expected price is NOT-MEASURED, never a silent pass (TK-11431 rule 1)
+ if (!(exp > 0)) unmeasured.push('expected price unknown (sku absent/invalid in payloads-imgfixed.jsonl)');
+ else if (!sellable.some(v => Number(v.price) === Math.round(exp * 100))) bad.push(`expected price ${(Math.round(exp * 100) / 100).toFixed(2)} absent (saw ${sellable.map(v => (Number(v.price) / 100).toFixed(2)).join(',')})`);
+ }
+ res.state = bad.length ? 'MEASURED-BAD' : unmeasured.length ? 'NOT-MEASURED' : 'MEASURED-OK';
+ res.reasons.push(...bad, ...unmeasured);
+ res.detail = { title: pr.title, available: vs.some(v => v.available), prices: vs.map(v => `${v.title}:${v.price}`) };
+ break;
+ } catch (e) {
+ if (a === 3) res.reasons.push(`error: ${String(e?.message ?? e).slice(0, 120)}`); // keep why it went NOT-MEASURED
+ await sleep(800 * (a + 1));
+ }
+ }
+ rows.push(res);
+ console.log(`${res.state.padEnd(14)} ${res.http ?? '---'} ${p.sku} ${p.handle}`);
+ await sleep(1000); // ~1 req/s
+}
+
+const tally = rows.reduce((o, r) => (o[r.state] = (o[r.state] || 0) + 1, o), {});
+const verdict = tally['MEASURED-BAD'] > 0 ? 'FAIL' : tally['NOT-MEASURED'] > 0 ? 'WARN' : 'PASS';
+const report = {
+ ticket: 'TK-11046', purpose: 'night-refill storefront PDP sample (read-only)',
+ built_at: new Date().toISOString(), population_sampled: rows.length, tally, verdict,
+ note_dalmatians: 'DWXH-255000/255001 = the 2 non-CLEAR (settlement-gated, Steve-approved override activated 09-02) — re-checked first in this sample',
+ rows,
+};
+const outPath = path.join(outDir, 'night-pdp-verify-20260918.json');
+fs.writeFileSync(outPath, JSON.stringify(report, null, 2));
+console.log(`\n[${verdict}] sampled=${rows.length} ${JSON.stringify(tally)}\nWROTE ${outPath}`);
\ No newline at end of file
← 8e87211 TK-10484: restore file for NU cost fix (4 rows 25->30)
·
back to Designerwallcoverings
·
lib/shopify: probe SHOPIFY_FULL_ACCESS_TOKEN once, fall back cb3710a →