[object Object]

← 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

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 →