← back to Wallco Ai
LA Toile: stage+publish 30 Nano Banana toiles to live (peer-user all_designs insert, settlement-override publish)
2ef18032b9c2b462796495a26b482fab07f24078 · 2026-06-03 08:07:15 -0700 · steve@designerwallcoverings.com
Files touched
A scripts/stage-la-toile-nb.js
Diff
commit 2ef18032b9c2b462796495a26b482fab07f24078
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date: Wed Jun 3 08:07:15 2026 -0700
LA Toile: stage+publish 30 Nano Banana toiles to live (peer-user all_designs insert, settlement-override publish)
---
scripts/stage-la-toile-nb.js | 77 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 77 insertions(+)
diff --git a/scripts/stage-la-toile-nb.js b/scripts/stage-la-toile-nb.js
new file mode 100644
index 0000000..9e21291
--- /dev/null
+++ b/scripts/stage-la-toile-nb.js
@@ -0,0 +1,77 @@
+// Stage the 30 Nano Banana LA Toile renders into the catalog as UNPUBLISHED rows
+// (mirrors generate_designs.js persistence). Run with --publish to also flip
+// is_published=TRUE (settlement-override) after staging. Verify before publishing.
+// Usage: node scripts/stage-la-toile-nb.js # stage only (is_published=FALSE)
+// node scripts/stage-la-toile-nb.js --publish # stage + publish to live DB
+const fs = require('fs');
+const path = require('path');
+const { spawnSync } = require('child_process');
+const ROOT = path.join(__dirname, '..');
+const OUT = path.join(ROOT, 'data', 'generated');
+const PUBLISH = process.argv.includes('--publish');
+
+// Connect as the local PEER user (owns the tables) — same as generate_designs.js.
+// dw_admin lacks write perms on the all_designs view.
+function psql(sql) {
+ const r = spawnSync('psql', ['dw_unified', '-At', '-q'], { input: sql, encoding: 'utf8' });
+ if (r.status !== 0) throw new Error(r.stderr || 'psql failed');
+ return r.stdout.trim();
+}
+function esc(s) { return String(s).replace(/'/g, "''"); }
+
+const SCENE_TITLES = [
+ 'Hollywood Sign','Venice Beach','Downtown LA','Sunset Boulevard','Beverly Hills Bungalows',
+ 'Santa Monica Pier','Chinatown Gate','Echo Park Lake','Case Study Houses','Mulholland Drive',
+ 'Watts Towers','LA Aqueduct','Olvera Street','Griffith Park','Malibu Coast',
+ 'Art-Deco Palaces','Citrus Groves','Union Station','Studio Backlot','Rose Parade',
+ 'Angels Flight','La Brea Tar Pits','Bradbury Building','Mulholland Coyote','Silver Lake',
+ 'Farmers Market','Topanga Canyon','LAX Theme Building','Dodger Stadium','Jacaranda Streets',
+];
+const INK_NAMES = ['Garden','Jacaranda','Bougainvillea','Pacific','Sunset','Blush Sunshine','Brass & Green','Noir & Rose'];
+
+function paletteOf(p) {
+ const py = `
+from PIL import Image
+from collections import Counter
+import json, sys
+img = Image.open(sys.argv[1]).convert('RGB'); img.thumbnail((300,300))
+pal = img.quantize(colors=5, method=Image.Quantize.MEDIANCUT).convert('RGB')
+px = list(pal.getdata()); cnt = Counter(px); tot = sum(cnt.values())
+print(json.dumps([{'hex':'#{:02x}{:02x}{:02x}'.format(*c),'pct':round(n/tot*100,2)} for c,n in cnt.most_common(5)]))`;
+ const r = spawnSync('python3', ['-c', py, p], { encoding: 'utf8' });
+ try { const pal = JSON.parse(r.stdout.trim()); return { pal, dom: pal[0]?.hex || null }; }
+ catch { return { pal: [], dom: null }; }
+}
+
+const ids = [];
+for (let i = 0; i < 30; i++) {
+ const src = `/tmp/nb-toile-${String(i + 1).padStart(2, '0')}.png`;
+ if (!fs.existsSync(src)) { console.log(`scene ${i + 1}: MISSING ${src} — skip`); continue; }
+ const fname = `nb_la_toile_${i + 1}_${Date.now()}.png`;
+ const dest = path.join(OUT, fname);
+ fs.copyFileSync(src, dest);
+ const { pal, dom } = paletteOf(dest);
+ const title = `${SCENE_TITLES[i]} Toile — ${INK_NAMES[i % 8]}`;
+ const prompt = `LA Toile de Jouy: ${SCENE_TITLES[i]} (${INK_NAMES[i % 8]} colorway), engraved Beverly Hills scenic toile`;
+ const sql = `
+INSERT INTO all_designs (kind, width_in, height_in, generator, prompt, seed, dominant_hex, palette, local_path, category, product_line, settlement_verdict, ai_title, is_published, user_removed)
+VALUES ('seamless_tile', 24, 24, 'gemini-2.5-flash-image', '${esc(prompt)}', ${1000 + i},
+ ${dom ? "'" + dom + "'" : 'NULL'}, '${esc(JSON.stringify(pal))}'::jsonb, '${esc(dest)}', 'la-toile', 'wallco', 'OK', '${esc(title)}', FALSE, FALSE)
+RETURNING id;`;
+ const id = parseInt(psql(sql), 10);
+ psql(`UPDATE all_designs SET image_url='/designs/img/by-id/${id}' WHERE id=${id};`);
+ ids.push(id);
+ console.log(`scene ${i + 1} "${title}" -> #${id} (dom ${dom})`);
+}
+fs.writeFileSync('/tmp/nb-toile-staged-ids.json', JSON.stringify(ids));
+console.log(`\nSTAGED ${ids.length} rows (is_published=FALSE): ${ids.join(',')}`);
+
+if (PUBLISH && ids.length) {
+ // Flip to published with the settlement override (the PG trigger no-ops the flip otherwise).
+ const pub = `BEGIN; SET LOCAL settlement.allow_override='true';
+UPDATE all_designs SET is_published=TRUE WHERE id IN (${ids.join(',')}) AND settlement_verdict='OK';
+COMMIT;`;
+ psql(pub);
+ const n = psql(`SELECT count(*) FROM all_designs WHERE id IN (${ids.join(',')}) AND is_published=TRUE;`);
+ console.log(`PUBLISHED (DB): ${n}/${ids.length} now is_published=TRUE`);
+}
← 07dab7d LA Toile: Nano Banana generator (literal scenic toile, 30/30
·
back to Wallco Ai
·
build-tif: Real-ESRGAN mural upscale stage (DTD verdict A) — 8dad76a →