[object Object]

← back to Designer Wallcoverings

Daisy Bennett: room-setting images (62 generated on Kamatera, attached to live PDPs at last position, room_setting tag)

ee576edf235ca2d10ee46eccabb83c2b42bbf698 · 2026-07-06 14:32:22 -0700 · Steve

Files touched

Diff

commit ee576edf235ca2d10ee46eccabb83c2b42bbf698
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jul 6 14:32:22 2026 -0700

    Daisy Bennett: room-setting images (62 generated on Kamatera, attached to live PDPs at last position, room_setting tag)
---
 .../attach-rooms-daisy-bennett.cjs                 | 27 ++++++++++++++++++++++
 scripts/wallquest-refresh/room-batch-kamatera.cjs  | 25 ++++++++++++++++++++
 .../set-increment-daisy-bennett.cjs                | 13 ++++++++---
 3 files changed, 62 insertions(+), 3 deletions(-)

diff --git a/scripts/wallquest-refresh/attach-rooms-daisy-bennett.cjs b/scripts/wallquest-refresh/attach-rooms-daisy-bennett.cjs
new file mode 100644
index 00000000..d050a97c
--- /dev/null
+++ b/scripts/wallquest-refresh/attach-rooms-daisy-bennett.cjs
@@ -0,0 +1,27 @@
+// Attach generated room images to their live Shopify PDPs (Steve-authorized). Idempotent via
+// the 'room_setting' tag. Reads /tmp/rooms-manifest.txt (sku|pid|width) + /tmp/rooms-out/<sku>.jpg.
+const fs = require('fs'); const https = require('https');
+const TOK = process.env.SHOPIFY_ADMIN_TOKEN; const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
+const LIMIT = parseInt(process.env.LIMIT || '999', 10);
+function api(method, path, body){return new Promise((res,rej)=>{const data=body?JSON.stringify(body):null;const r=https.request({hostname:DOMAIN,path:`/admin/api/2024-10/${path}`,method,headers:{'X-Shopify-Access-Token':TOK,'Content-Type':'application/json',...(data?{'Content-Length':Buffer.byteLength(data)}:{})},timeout:45000},rs=>{let d='';rs.on('data',c=>d+=c);rs.on('end',()=>{try{const j=d?JSON.parse(d):{};rs.statusCode<300?res(j):rej(new Error(rs.statusCode+' '+d.slice(0,120)))}catch(e){rej(e)}})});r.on('error',rej);r.on('timeout',()=>{r.destroy();rej(new Error('timeout'))});if(data)r.write(data);r.end()})}
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+(async()=>{
+  const rows = fs.readFileSync('/tmp/rooms-manifest.txt','utf8').trim().split('\n').map(l=>l.split('|'));
+  let ok=0, skip=0, fail=0, i=0;
+  for (const [sku, pid] of rows) {
+    if (i++ >= LIMIT) break;
+    const img = `/tmp/rooms-out/${sku}.jpg`;
+    if (!fs.existsSync(img)) { skip++; continue; }
+    try {
+      const cur = await api('GET', `products/${pid}.json?fields=tags,images`);
+      if ((cur.product.tags||'').split(',').map(s=>s.trim()).includes('room_setting')) { skip++; continue; } // idempotent
+      const attachment = fs.readFileSync(img).toString('base64');
+      await api('POST', `products/${pid}/images.json`, { image: { attachment, alt: `${sku} room setting` } });
+      const tags = (cur.product.tags||'').split(',').map(s=>s.trim()).filter(Boolean); tags.push('room_setting');
+      await api('PUT', `products/${pid}.json`, { product: { id: Number(pid), tags: tags.join(', ') } });
+      ok++; if (ok<=3 || ok%15===0) console.log(`  ✅ ${sku} room attached (${pid})`);
+      await sleep(300);
+    } catch(e){ console.error(`  ❌ ${sku}: ${String(e).slice(0,120)}`); fail++; }
+  }
+  console.log(`\nATTACH DONE ok=${ok} skip=${skip} fail=${fail}`);
+})().catch(e=>{console.error('FATAL',e.message);process.exit(1);});
diff --git a/scripts/wallquest-refresh/room-batch-kamatera.cjs b/scripts/wallquest-refresh/room-batch-kamatera.cjs
new file mode 100644
index 00000000..6de2dd48
--- /dev/null
+++ b/scripts/wallquest-refresh/room-batch-kamatera.cjs
@@ -0,0 +1,25 @@
+// Runs ON Kamatera: loop /tmp/rooms-in/*.jpg → POST :3076/api/generate-room → /tmp/rooms-out/<sku>.png
+const fs = require('fs'); const http = require('http');
+const IN = '/tmp/rooms-in', OUT = '/tmp/rooms-out';
+fs.mkdirSync(OUT, { recursive: true });
+const ROOMS = ['living_room','bedroom','dining_room','office'];
+const ANGLES = ['angled_left','straight_on','angled_right'];
+const files = fs.readdirSync(IN).filter(f => f.endsWith('.jpg'));
+function gen(body){return new Promise((res,rej)=>{const data=JSON.stringify(body);const r=http.request({host:'127.0.0.1',port:3076,path:'/api/generate-room',method:'POST',headers:{'Content-Type':'application/json','Content-Length':Buffer.byteLength(data)},timeout:60000},rs=>{let d='';rs.on('data',c=>d+=c);rs.on('end',()=>{try{res(JSON.parse(d))}catch(e){rej(new Error('badjson '+d.slice(0,80)))}})});r.on('error',rej);r.on('timeout',()=>{r.destroy();rej(new Error('timeout'))});r.write(data);r.end()})}
+(async()=>{
+  let ok=0, fail=0, i=0;
+  for (const f of files) {
+    const sku = f.replace('.jpg','');
+    const out = `${OUT}/${sku}.png`;
+    if (fs.existsSync(out)) { ok++; continue; } // idempotent
+    try {
+      const b64 = fs.readFileSync(`${IN}/${f}`).toString('base64');
+      const j = await gen({ patternBase64:b64, roomType:ROOMS[i%ROOMS.length], angle:ANGLES[i%ANGLES.length], cameraDistance:8, patternWidth:36, patternHeight:36 });
+      if (j.success && j.image) { fs.writeFileSync(out, Buffer.from(j.image.replace(/^data:image\/\w+;base64,/,''),'base64')); ok++; }
+      else { fail++; console.log('FAIL',sku, JSON.stringify(j).slice(0,80)); }
+    } catch(e){ fail++; console.log('ERR',sku,String(e).slice(0,60)); }
+    i++;
+    if (i%10===0) console.log(`[${i}/${files.length}] ok=${ok} fail=${fail}`);
+  }
+  console.log(`BATCH DONE ok=${ok} fail=${fail} → ${OUT}`);
+})();
diff --git a/scripts/wallquest-refresh/set-increment-daisy-bennett.cjs b/scripts/wallquest-refresh/set-increment-daisy-bennett.cjs
index 7ff3b7e6..15d32913 100644
--- a/scripts/wallquest-refresh/set-increment-daisy-bennett.cjs
+++ b/scripts/wallquest-refresh/set-increment-daisy-bennett.cjs
@@ -30,9 +30,16 @@ async function apply(pid) {
   if (!tags.includes('display_variant')) { tags.push('display_variant'); await shopify('PUT', `products/${pid}.json`, { product:{ id:Number(pid), tags: tags.join(', ') } }); }
 }
 (async () => {
-  const daisy = execSync(`PGPASSWORD=DW2024SecurePass psql -h 127.0.0.1 -U dw_admin -d dw_unified -tAc "SELECT shopify_product_id FROM daisy_bennett_catalog WHERE on_shopify AND shopify_product_id IS NOT NULL ORDER BY dw_sku"`).toString().trim().split('\n').filter(Boolean);
-  const ids = [...daisy, ...EXTRA_IDS].slice(0, LIMIT);
-  console.log(`Setting ${INC}-yard increments on ${ids.length} products (${daisy.length} Daisy + ${EXTRA_IDS.length} extra)\n`);
+  // SOURCE_TABLE env lets this run against carl_robinson_catalog too; IDS env = explicit id list (skips DB).
+  let ids;
+  if (process.env.IDS) { ids = process.env.IDS.split(',').filter(Boolean); }
+  else {
+    const table = process.env.SOURCE_TABLE || 'daisy_bennett_catalog';
+    const rows = execSync(`PGPASSWORD=DW2024SecurePass psql -h 127.0.0.1 -U dw_admin -d dw_unified -tAc "SELECT shopify_product_id FROM ${table} WHERE on_shopify AND shopify_product_id IS NOT NULL ORDER BY dw_sku"`).toString().trim().split('\n').filter(Boolean);
+    ids = [...rows, ...EXTRA_IDS];
+  }
+  ids = ids.slice(0, LIMIT);
+  console.log(`Setting ${INC}-yard increments on ${ids.length} products (source=${process.env.IDS?'IDS':process.env.SOURCE_TABLE||'daisy_bennett_catalog'})\n`);
   let ok=0, fail=0;
   for (const pid of ids) {
     try { await apply(pid); ok++; if (ok<=3 || ok%20===0) console.log(`  ✅ ${pid} → min ${INC} / step ${INC} yd + display_variant`); }

← 080ee652 Kravet-under-PR re-vendor tool (dry-run default; --apply/--a  ·  back to Designer Wallcoverings  ·  wallquest write.cjs: also persist Roll Length (per-item unit cb2e90b9 →