← back to Designer Wallcoverings
WallQuest onboard: swatch DL + local qwen2.5vl enrich + rooms + settlement gate
8de32f959893dfcc753fa3919a1b7420bad269ec · 2026-07-09 23:08:09 -0700 · Steve
- dl-swatches.cjs: download swatches, backfill local_image_path (187, 0 bad)
- enrich-book.py: local qwen2.5vl vision enrich ($0), luxury desc + colors + hex + styles
- 187 patch-quilt rooms generated ($0); room_setting_images backfilled
- Daisy v2 settlement gate: 7 florals/leaves flagged REVIEW (stay DRAFT), 45 clear
- image dirs gitignored
Files touched
A scripts/wallquest-refresh/daisy-v2-onboard-viewer/.gitignoreA scripts/wallquest-refresh/dl-swatches.cjsA scripts/wallquest-refresh/enrich-book.pyA scripts/wallquest-refresh/lillian-august-onboard-viewer/.gitignore
Diff
commit 8de32f959893dfcc753fa3919a1b7420bad269ec
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jul 9 23:08:09 2026 -0700
WallQuest onboard: swatch DL + local qwen2.5vl enrich + rooms + settlement gate
- dl-swatches.cjs: download swatches, backfill local_image_path (187, 0 bad)
- enrich-book.py: local qwen2.5vl vision enrich ($0), luxury desc + colors + hex + styles
- 187 patch-quilt rooms generated ($0); room_setting_images backfilled
- Daisy v2 settlement gate: 7 florals/leaves flagged REVIEW (stay DRAFT), 45 clear
- image dirs gitignored
---
.../daisy-v2-onboard-viewer/.gitignore | 2 ++
scripts/wallquest-refresh/dl-swatches.cjs | 35 +++++++++++++++++++
scripts/wallquest-refresh/enrich-book.py | 39 ++++++++++++++++++++++
.../lillian-august-onboard-viewer/.gitignore | 2 ++
4 files changed, 78 insertions(+)
diff --git a/scripts/wallquest-refresh/daisy-v2-onboard-viewer/.gitignore b/scripts/wallquest-refresh/daisy-v2-onboard-viewer/.gitignore
new file mode 100644
index 00000000..b81752c9
--- /dev/null
+++ b/scripts/wallquest-refresh/daisy-v2-onboard-viewer/.gitignore
@@ -0,0 +1,2 @@
+img/
+rooms-local/
diff --git a/scripts/wallquest-refresh/dl-swatches.cjs b/scripts/wallquest-refresh/dl-swatches.cjs
new file mode 100644
index 00000000..4d68d48c
--- /dev/null
+++ b/scripts/wallquest-refresh/dl-swatches.cjs
@@ -0,0 +1,35 @@
+// Download the first product image per SKU -> local swatch, backfill local_image_path.
+// Env: TABLE, VIEW (viewer dir; writes img/{dw_sku}.jpg). WallQuest thumbs aren't hotlink-blocked.
+const fs = require('fs'), https = require('https'), http = require('http'), { execSync } = require('child_process');
+const TABLE = process.env.TABLE, VIEW = process.env.VIEW;
+const CONN = process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified';
+const IMG = `${VIEW}/img`; fs.mkdirSync(IMG, { recursive: true });
+const psql = sql => execSync(`psql "${CONN}" -tAF'|' -c "${sql.replace(/"/g,'\\"')}"`, { encoding: 'utf8' });
+const get = url => new Promise((res, rej) => {
+ const mod = url.startsWith('https') ? https : http;
+ const req = mod.get(url, { headers: { 'User-Agent': 'Mozilla/5.0', 'Referer': 'https://www.wallquest.com/' }, timeout: 30000 }, r => {
+ if (r.statusCode >= 300 && r.headers.location) return get(new URL(r.headers.location, url).href).then(res, rej);
+ if (r.statusCode !== 200) { r.resume(); return rej(new Error('http ' + r.statusCode)); }
+ const bufs = []; r.on('data', c => bufs.push(c)); r.on('end', () => res(Buffer.concat(bufs)));
+ });
+ req.on('error', rej); req.on('timeout', () => { req.destroy(); rej(new Error('timeout')); });
+});
+
+(async () => {
+ const rows = psql(`SELECT dw_sku, COALESCE(all_images::text,'[]') FROM ${TABLE} WHERE all_images IS NOT NULL`).trim().split('\n').filter(Boolean);
+ let ok = 0, bad = 0; const sqls = [];
+ for (const line of rows) {
+ const [sku, imgsRaw] = line.split('|');
+ let imgs = []; try { imgs = JSON.parse(imgsRaw); } catch {}
+ const url = imgs.find(u => u && /jpe?g|png/i.test(u));
+ if (!url) { bad++; continue; }
+ const dest = `${IMG}/${sku}.jpg`, rel = `img/${sku}.jpg`;
+ if (!fs.existsSync(dest)) {
+ try { const buf = await get(url); if (buf.length < 2000) { bad++; continue; } fs.writeFileSync(dest, buf); ok++; }
+ catch (e) { bad++; continue; }
+ } else ok++;
+ sqls.push(`UPDATE ${TABLE} SET local_image_path='${JSON.stringify([rel])}'::jsonb WHERE dw_sku='${sku}';`);
+ }
+ if (sqls.length) { fs.writeFileSync('/tmp/_dl.sql', sqls.join('\n')); execSync(`psql "${CONN}" -q -f /tmp/_dl.sql`); }
+ console.log(`${TABLE}: swatches ok=${ok} bad=${bad} -> ${IMG}`);
+})();
diff --git a/scripts/wallquest-refresh/enrich-book.py b/scripts/wallquest-refresh/enrich-book.py
new file mode 100644
index 00000000..83109b3f
--- /dev/null
+++ b/scripts/wallquest-refresh/enrich-book.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+# Local vision enrich (qwen2.5vl via Ollama, $0): per SKU, read the swatch -> 2-sentence luxury
+# description + dominant color names + hex + styles. Updates catalog. Resumable (skips enriched).
+import base64, json, os, subprocess, urllib.request, sys
+TABLE = os.environ['TABLE']; VIEW = os.environ['VIEW']
+CONN = os.environ.get('DATABASE_URL', 'postgresql://dw_admin@127.0.0.1:5432/dw_unified')
+MODEL = 'qwen2.5vl:7b'
+def psql(sql): return subprocess.run(['psql', CONN, '-tAF|', '-c', sql], capture_output=True, text=True).stdout
+def esc(s): return "'" + str(s).replace("'", "''") + "'" if s is not None else 'NULL'
+
+rows = [l.split('|') for l in psql(
+ f"SELECT dw_sku, COALESCE(pattern_name,''), COALESCE(color_name,''), COALESCE(material,''), COALESCE((local_image_path::jsonb)->>0,'') "
+ f"FROM {TABLE} WHERE (ai_description IS NULL OR ai_description='') ORDER BY dw_sku").strip().split('\n') if '|' in l]
+print(f'{TABLE}: {len(rows)} to enrich', flush=True)
+done = 0
+for sku, pat, color, mat, img in rows:
+ p = f'{VIEW}/{img}'
+ if not img or not os.path.exists(p): continue
+ prompt = (f"This is a {mat} wallcovering swatch named '{pat}' in colorway '{color}'. Write a 2-sentence "
+ f"luxury product description for an interior-design audience (no brand names, no vendor names). Then list "
+ f"the dominant colors, a hex for the main color, and 2-3 interior styles it suits. Respond ONLY JSON: "
+ f'{{"description":"...","colors":["..."],"hex":"#RRGGBB","styles":["..."]}}')
+ try:
+ body = json.dumps({"model": MODEL, "prompt": prompt, "images": [base64.b64encode(open(p,'rb').read()).decode()],
+ "format": "json", "stream": False, "options": {"temperature": 0.2}}).encode()
+ r = json.load(urllib.request.urlopen(urllib.request.Request(
+ "http://localhost:11434/api/generate", body, {"Content-Type": "application/json"}), timeout=240))
+ j = json.loads(r["response"])
+ desc = (j.get('description') or '').replace('Wallpaper', 'Wallcovering')
+ sql = (f"UPDATE {TABLE} SET ai_description={esc(desc)}, "
+ f"ai_colors={esc(json.dumps(j.get('colors') or []))}::jsonb, "
+ f"color_hex={esc(j.get('hex') or '')}, ai_styles={esc(json.dumps(j.get('styles') or []))}::jsonb, "
+ f"updated_at=now() WHERE dw_sku={esc(sku)}")
+ subprocess.run(['psql', CONN, '-q', '-c', sql], capture_output=True)
+ done += 1
+ if done % 20 == 0: print(f' {done}/{len(rows)}', flush=True)
+ except Exception as e:
+ print(f' ERR {sku}: {str(e)[:60]}', flush=True)
+print(f'{TABLE}: ENRICHED {done}', flush=True)
diff --git a/scripts/wallquest-refresh/lillian-august-onboard-viewer/.gitignore b/scripts/wallquest-refresh/lillian-august-onboard-viewer/.gitignore
new file mode 100644
index 00000000..b81752c9
--- /dev/null
+++ b/scripts/wallquest-refresh/lillian-august-onboard-viewer/.gitignore
@@ -0,0 +1,2 @@
+img/
+rooms-local/
← 3483fed8 WallQuest real-textile onboard pipeline: normalize + load +
·
back to Designer Wallcoverings
·
catalog-push: diff-based redesign w/ two-bucket safety (sync 65004b34 →