← back to Designer Wallcoverings
auto-save: 2026-07-10T12:08:12 (6 files) — DW-Programming/schu_pillow_apisniff.mjs pending-approval/boost-filter-consolidation-2026-06-25 scripts/pr-contact-for-price shopify/scripts/cadence/data/upload-restore-2026-07-10.jsonl vendor-scrapers/china-seas-refresh
45d52c9333ac7b0ff417d21515f25c3c8b6cb3f4 · 2026-07-10 12:08:18 -0700 · Steve Abrams
Files touched
A DW-Programming/schu-enrich-local.mjsD DW-Programming/schu_pillow_apisniff.mjsM shopify/scripts/cadence/data/upload-restore-2026-07-10.jsonl
Diff
commit 45d52c9333ac7b0ff417d21515f25c3c8b6cb3f4
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Jul 10 12:08:18 2026 -0700
auto-save: 2026-07-10T12:08:12 (6 files) — DW-Programming/schu_pillow_apisniff.mjs pending-approval/boost-filter-consolidation-2026-06-25 scripts/pr-contact-for-price shopify/scripts/cadence/data/upload-restore-2026-07-10.jsonl vendor-scrapers/china-seas-refresh
---
DW-Programming/schu-enrich-local.mjs | 70 ++++++++++++++++++++++
DW-Programming/schu_pillow_apisniff.mjs | 32 ----------
.../cadence/data/upload-restore-2026-07-10.jsonl | 3 +
3 files changed, 73 insertions(+), 32 deletions(-)
diff --git a/DW-Programming/schu-enrich-local.mjs b/DW-Programming/schu-enrich-local.mjs
new file mode 100644
index 00000000..e886186f
--- /dev/null
+++ b/DW-Programming/schu-enrich-local.mjs
@@ -0,0 +1,70 @@
+// LOCAL enrichment ($0) for new Schumacher pillow rows.
+// qwen2.5vl:7b -> colors + background from image; qwen3:14b -> polished 1-line description.
+// Writes ONLY ai_colors, ai_background_color, ai_description on schumacher_catalog pillow rows.
+import pg from 'pg';
+
+const OLLAMA = 'http://127.0.0.1:11434';
+const LIMIT = parseInt(process.argv[2] || '40');
+const sleep = (ms) => new Promise(r => setTimeout(r, ms));
+
+async function fetchImgB64(url) {
+ const r = await fetch(url);
+ if (!r.ok) return null;
+ const buf = Buffer.from(await r.arrayBuffer());
+ return buf.toString('base64');
+}
+async function ollama(model, prompt, images) {
+ const body = { model, prompt, stream: false, options: { temperature: 0.2 } };
+ if (images) body.images = images;
+ const r = await fetch(`${OLLAMA}/api/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
+ if (!r.ok) return null;
+ const d = await r.json();
+ return d.response;
+}
+function extractJSON(s) {
+ if (!s) return null;
+ const m = s.match(/\{[\s\S]*\}/);
+ if (!m) return null;
+ try { return JSON.parse(m[0]); } catch { return null; }
+}
+
+const client = new pg.Client({ database: 'dw_unified' });
+await client.connect();
+const { rows } = await client.query(
+ `SELECT mfr_sku, pattern_name, color_name, image_url, description
+ FROM schumacher_catalog
+ WHERE product_type='pillow' AND image_url IS NOT NULL
+ AND (ai_colors IS NULL OR ai_background_color IS NULL)
+ ORDER BY crawled_at DESC LIMIT $1`, [LIMIT]);
+console.log(`enriching ${rows.length} pillow rows (local, $0)`);
+
+let done = 0;
+for (const r of rows) {
+ try {
+ const b64 = await fetchImgB64(r.image_url);
+ let colors = null, bg = null;
+ if (b64) {
+ const vprompt = `This is a decorative throw pillow. Return ONLY JSON: {"colors":["name1","name2","name3"],"background":"dominant background color name"}. Use plain color names (e.g. Navy, Cream, Sage). Max 3 colors.`;
+ const vout = await ollama('qwen2.5vl:7b', vprompt, [b64]);
+ const j = extractJSON(vout);
+ if (j) { colors = j.colors || null; bg = j.background || null; }
+ }
+ // polished description via qwen3 (text only)
+ const tprompt = `Write ONE concise retail sentence (max 22 words) for a luxury Schumacher throw pillow named "${r.pattern_name}" in color "${r.color_name}". No fluff, no quotes. Description context: ${(r.description || '').slice(0, 160)}`;
+ let desc = await ollama('qwen3:14b', tprompt, null);
+ if (desc) desc = desc.replace(/<think>[\s\S]*?<\/think>/gi, '').trim().replace(/^["']|["']$/g, '').split('\n').filter(Boolean).pop();
+
+ await client.query(
+ `UPDATE schumacher_catalog SET
+ ai_colors = COALESCE($2::jsonb, ai_colors),
+ ai_background_color = COALESCE($3, ai_background_color),
+ ai_description = COALESCE($4, ai_description),
+ ai_accepted_at = now()
+ WHERE mfr_sku = $1`,
+ [r.mfr_sku, colors ? JSON.stringify(colors) : null, bg, desc || null]);
+ done++;
+ if (done % 5 === 0) process.stdout.write(` ${done}/${rows.length}\r`);
+ } catch (e) { console.error('enrich fail', r.mfr_sku, e.message); }
+}
+await client.end();
+console.log(`\nlocal enrichment done: ${done} rows`);
diff --git a/DW-Programming/schu_pillow_apisniff.mjs b/DW-Programming/schu_pillow_apisniff.mjs
deleted file mode 100644
index 57491f60..00000000
--- a/DW-Programming/schu_pillow_apisniff.mjs
+++ /dev/null
@@ -1,32 +0,0 @@
-// ANONYMOUS (no login) — load pillows category, capture the client JSON API that returns variations,
-// then replay it page-by-page to harvest all 782 pillow variations into a JSONL.
-import { chromium } from 'playwright';
-import fs from 'fs';
-
-const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36';
-const browser = await chromium.launch({ headless: true });
-const ctx = await browser.newContext({ userAgent: UA });
-const page = await ctx.newPage();
-
-let apiUrl = null;
-let apiHeaders = null;
-const seen = [];
-page.on('request', (r) => {
- const u = r.url();
- if (/variation|products\/search|catalog.*product|api/i.test(u) && r.resourceType() === 'fetch') {
- seen.push(r.method() + ' ' + u.slice(0, 160));
- if (/variation|search/i.test(u) && !apiUrl) { apiUrl = u; apiHeaders = r.headers(); }
- }
-});
-
-await page.goto('https://schumacher.com/catalog/7', { waitUntil: 'domcontentloaded', timeout: 40000 });
-await page.waitForTimeout(6000);
-// try to trigger pagination / more loads by scrolling
-for (let i = 0; i < 3; i++) { await page.mouse.wheel(0, 4000); await page.waitForTimeout(1500); }
-
-console.log('=== fetch requests seen ===');
-console.log(seen.slice(0, 20).join('\n'));
-console.log('=== candidate apiUrl ===', apiUrl);
-if (apiHeaders) console.log('=== apiHeaders ===', JSON.stringify({ authorization: apiHeaders.authorization, 'x-api-key': apiHeaders['x-api-key'], origin: apiHeaders.origin, referer: apiHeaders.referer }));
-
-await browser.close();
diff --git a/shopify/scripts/cadence/data/upload-restore-2026-07-10.jsonl b/shopify/scripts/cadence/data/upload-restore-2026-07-10.jsonl
index 48f814dd..8074778e 100644
--- a/shopify/scripts/cadence/data/upload-restore-2026-07-10.jsonl
+++ b/shopify/scripts/cadence/data/upload-restore-2026-07-10.jsonl
@@ -168,3 +168,6 @@
{"table":"thibaut_catalog","mfr_sku":"T24128","dw_sku":"DWTT-73944","shopify_product_id":"7880634957875","action":"created","activated":true,"published":true,"sampleOnly":false,"status":"ACTIVE","batch_id":"upload-am-2026-07-10T17-40-05-236Z","ts":"2026-07-10T17:40:11.113Z"}
{"table":"thibaut_catalog","mfr_sku":"T24124","dw_sku":"DWTT-73945","shopify_product_id":"7880634990643","action":"created","activated":true,"published":true,"sampleOnly":false,"status":"ACTIVE","batch_id":"upload-am-2026-07-10T17-40-05-236Z","ts":"2026-07-10T17:40:14.441Z"}
{"table":"thibaut_catalog","mfr_sku":"T24129","dw_sku":"DWTT-73946","shopify_product_id":"7880635023411","action":"created","activated":true,"published":true,"sampleOnly":false,"status":"ACTIVE","batch_id":"upload-am-2026-07-10T17-40-05-236Z","ts":"2026-07-10T17:40:17.807Z"}
+{"table":"thibaut_catalog","mfr_sku":"T24130","dw_sku":"DWTT-73947","shopify_product_id":"7880662745139","action":"created","activated":true,"published":true,"sampleOnly":false,"status":"ACTIVE","batch_id":"upload-am-2026-07-10T18-40-00-247Z","ts":"2026-07-10T18:40:05.619Z"}
+{"table":"thibaut_catalog","mfr_sku":"T24131","dw_sku":"DWTT-73948","shopify_product_id":"7880662843443","action":"created","activated":true,"published":true,"sampleOnly":false,"status":"ACTIVE","batch_id":"upload-am-2026-07-10T18-40-00-247Z","ts":"2026-07-10T18:40:09.143Z"}
+{"table":"thibaut_catalog","mfr_sku":"T24134","dw_sku":"DWTT-73949","shopify_product_id":"7880662876211","action":"created","activated":true,"published":true,"sampleOnly":false,"status":"ACTIVE","batch_id":"upload-am-2026-07-10T18-40-00-247Z","ts":"2026-07-10T18:40:12.866Z"}
← 4bfcd4e0 auto-save: 2026-07-10T11:38:04 (7 files) — pending-approval/
·
back to Designer Wallcoverings
·
auto-save: 2026-07-10T12:38:21 (4 files) — _dw-login-record. 0b3ab024 →