← back to Designer Wallcoverings
wallquest grasscloth/veneers: login fix + Phillipe Romano relabel
41f0ede8a0ed2b971dfc93e0f4003c1a1a77634b · 2026-07-06 11:59:16 -0700 · Steve
- scrape-grasscloth.cjs: FIX login (button/Enter submit + 14s price-AJAX wait + per-session
loginVerified()); old 9s scrape silently missed every login-gated price
- restage-phillipe-romano.cjs: relabel to GRS-/MIC-/CORK- (grasses/mica/cork) by material
- 87 SKUs moved to phillipe_romano_catalog (isolated from Malibu), all 87 priced, 0 PL leaks
Files touched
A scripts/wallquest-refresh/restage-phillipe-romano.cjsM scripts/wallquest-refresh/scrape-grasscloth.cjs
Diff
commit 41f0ede8a0ed2b971dfc93e0f4003c1a1a77634b
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Jul 6 11:59:16 2026 -0700
wallquest grasscloth/veneers: login fix + Phillipe Romano relabel
- scrape-grasscloth.cjs: FIX login (button/Enter submit + 14s price-AJAX wait + per-session
loginVerified()); old 9s scrape silently missed every login-gated price
- restage-phillipe-romano.cjs: relabel to GRS-/MIC-/CORK- (grasses/mica/cork) by material
- 87 SKUs moved to phillipe_romano_catalog (isolated from Malibu), all 87 priced, 0 PL leaks
---
.../wallquest-refresh/restage-phillipe-romano.cjs | 57 ++++++++++++++++++++++
scripts/wallquest-refresh/scrape-grasscloth.cjs | 27 ++++++++--
2 files changed, 81 insertions(+), 3 deletions(-)
diff --git a/scripts/wallquest-refresh/restage-phillipe-romano.cjs b/scripts/wallquest-refresh/restage-phillipe-romano.cjs
new file mode 100644
index 00000000..d7d96355
--- /dev/null
+++ b/scripts/wallquest-refresh/restage-phillipe-romano.cjs
@@ -0,0 +1,57 @@
+// Relabel the 87 staged rows: DWQW-* -> Phillipe Romano GRS-/MIC-/CORK- by material.
+// Overlay scraped costs. UPDATE by mfr_sku (stable key). Vendor intent = Phillipe Romano (set at publish).
+// GRS = grasses/paperweaves, CORK = cork, MIC = mica (Metal Leaf mapped here, flagged).
+const fs = require('fs');
+const { execSync } = require('child_process');
+const PSQL = '/opt/homebrew/opt/postgresql@14/bin/psql';
+const ENVL = fs.readFileSync(require('os').homedir() + '/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/.env.local', 'utf8');
+const PGPW = (ENVL.match(/^POSTGRES_PASSWORD=(.+)$/m) || [])[1];
+const q = sql => execSync(`PGPASSWORD='${PGPW}' ${PSQL} -h localhost -U dw_admin -d dw_unified -tAF'|' -c "${sql.replace(/"/g,'\\"')}"`, { encoding: 'utf8', maxBuffer: 1e8 });
+
+// prices from the login-fixed scrape
+const priced = {};
+try { fs.readFileSync('/tmp/wq-grasscloth-priced.jsonl', 'utf8').split('\n').filter(Boolean).map(JSON.parse).forEach(r => { if (r.priceValue) priced[r.mfr_sku] = parseFloat(String(r.priceValue).replace(/,/g, '')); }); } catch {}
+
+// the 87 staged rows (mfr_sku + material), ordered stable
+const rows = q("SELECT mfr_sku||'|'||coalesce(material,'') FROM wallquest_catalog WHERE dw_sku ~ '^DWQW-614[5-9][0-9]|^DWQW-615[0-3][0-9]' ORDER BY mfr_sku;")
+ .split('\n').filter(Boolean).map(l => { const [mfr_sku, material] = l.split('|'); return { mfr_sku, material }; });
+
+const prefixOf = m => {
+ const s = (m || '').toLowerCase();
+ if (/cork/.test(s)) return 'CORK';
+ if (/metal|mica|leaf/.test(s)) return 'MIC'; // mica family (Metal Leaf -> MIC, flagged)
+ return 'GRS'; // grasscloth / naturals / paperweave / default
+};
+// recent-block starts (max+1)
+const next = { GRS: 98619, MIC: 98625, CORK: 99024 };
+
+const updates = rows.map(r => {
+ const pfx = prefixOf(r.material);
+ const dw = `${pfx}-${next[pfx]++}`;
+ const cost = priced[r.mfr_sku] || null;
+ const our = cost ? +(cost / 0.65 / 0.85).toFixed(2) : null; // standard DW retail
+ const pdw = cost ? +(cost / 0.55).toFixed(2) : null; // WallQuest-convention retail field
+ return { mfr_sku: r.mfr_sku, dw, pfx, cost, our, pdw };
+});
+
+// build one UPDATE ... FROM (VALUES ...) statement
+const sq = s => "'" + String(s).replace(/'/g, "''") + "'";
+const vals = updates.map(u => `(${sq(u.mfr_sku)},${sq(u.dw)},${u.cost == null ? 'NULL' : u.cost},${u.our == null ? 'NULL' : u.our},${u.pdw == null ? 'NULL' : u.pdw})`).join(',\n');
+const stmt = `WITH d(mfr_sku,dw,cost,our,pdw) AS (VALUES ${vals})
+UPDATE wallquest_catalog c SET
+ dw_sku = d.dw,
+ price_retail = COALESCE(d.cost, c.price_retail),
+ our_price = COALESCE(d.our, c.our_price),
+ price_dw = COALESCE(d.pdw, c.price_dw),
+ net_cost = COALESCE(d.cost, c.net_cost),
+ updated_at = now()
+FROM d WHERE c.mfr_sku = d.mfr_sku;`;
+fs.writeFileSync('/tmp/wq-restage-pr.sql', stmt);
+const res = execSync(`PGPASSWORD='${PGPW}' ${PSQL} -h localhost -U dw_admin -d dw_unified -c "$(cat /tmp/wq-restage-pr.sql)"`, { encoding: 'utf8', maxBuffer: 1e8 });
+const byPfx = updates.reduce((a, u) => { a[u.pfx] = (a[u.pfx] || 0) + 1; return a; }, {});
+const priceCount = updates.filter(u => u.cost).length;
+console.log('UPDATE:', res.trim());
+console.log('by prefix:', JSON.stringify(byPfx));
+console.log('priced:', priceCount, '/', updates.length);
+console.log('MIC (Metal Leaf, flagged for review):', updates.filter(u => u.pfx === 'MIC').map(u => `${u.dw}=${u.mfr_sku}`).join(', '));
+console.log('ranges:', ['GRS', 'MIC', 'CORK'].map(p => { const xs = updates.filter(u => u.pfx === p).map(u => u.dw); return xs.length ? `${xs[0]}..${xs[xs.length - 1]}` : `${p}:none`; }).join(' | '));
diff --git a/scripts/wallquest-refresh/scrape-grasscloth.cjs b/scripts/wallquest-refresh/scrape-grasscloth.cjs
index 7f4302ab..fe416440 100644
--- a/scripts/wallquest-refresh/scrape-grasscloth.cjs
+++ b/scripts/wallquest-refresh/scrape-grasscloth.cjs
@@ -28,9 +28,29 @@ async function login(page) {
await page.waitForTimeout(8000);
await page.fill('#Email', 'info@designerwallcoverings.com');
await page.fill('#Password', WPW);
- await page.press('#Password', 'Enter');
+ // FIX 2026-07-06: the login BUTTON submits; press-Enter silently no-ops (price stays login-gated).
+ let clicked = false;
+ for (const sel of ['input.login-button', '.login-button', 'button.login-button']) {
+ if (await page.$(sel)) { try { await page.click(sel, { timeout: 5000 }); clicked = true; break; } catch (e) {} }
+ }
+ if (!clicked) await page.press('#Password', 'Enter');
await page.waitForTimeout(9000);
}
+// verify login by loading a KNOWN-priced product; returns true if a real price rendered
+async function loginVerified(page, tries = 3) {
+ for (let t = 0; t < tries; t++) {
+ await login(page);
+ await goto(page, 'https://www.wallquest.com/sy21843');
+ await page.waitForTimeout(parseInt(process.env.WQ_PWAIT || '14000'));
+ const gotPrice = await page.evaluate(() => {
+ const pv = ([...document.querySelectorAll('[class*=price-value]')][0] || {}).textContent || '';
+ return /[\d,]+\.\d{2}/.test(pv) && !/sign in to view/i.test(document.body.innerText);
+ });
+ console.error(` login attempt ${t + 1}: priceVisible=${gotPrice}`);
+ if (gotPrice) return true;
+ }
+ return false;
+}
const extract = p => p.evaluate(() => {
const Q = s => [...document.querySelectorAll(s)];
const txt = s => (Q(s)[0]?.textContent || '').trim();
@@ -61,11 +81,12 @@ const extract = p => p.evaluate(() => {
br = await chromium.connectOverCDP(s.connectUrl);
const ctx = br.contexts()[0];
const page = ctx.pages()[0] || await ctx.newPage();
- await login(page);
+ const authed = await loginVerified(page);
+ if (!authed) { console.error(' !! login unverified this session, skipping chunk to avoid unpriced rows'); continue; }
for (const r of chunk) {
try {
if (!await goto(page, r.url)) { out.write(JSON.stringify({ mfr_sku: r.mfr_sku, url: r.url, nav: 0 }) + '\n'); done++; continue; }
- await page.waitForTimeout(9000);
+ await page.waitForTimeout(parseInt(process.env.WQ_PWAIT || '13000'));
const x = await extract(page);
if (x.priceValue) priced++;
if (x.imgs && x.imgs.length) imaged++;
← bccb98f8 Carl Robinson: material-based SKU prefixes (DWGRS/PWV/CORK/M
·
back to Designer Wallcoverings
·
Daisy Bennett: adjudicate flagged families — Diamond Back We 83f25e3d →