[object Object]

← back to Wallco Ai

scripts: --start/--count CLI flags on geometric-block-leaves generator + pilot scorer

50fe0af7906a979c450a14985460f4587ae97324 · 2026-05-26 08:25:59 -0700 · Steve Abrams

Files touched

Diff

commit 50fe0af7906a979c450a14985460f4587ae97324
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 26 08:25:59 2026 -0700

    scripts: --start/--count CLI flags on geometric-block-leaves generator + pilot scorer
---
 scripts/generate-geometric-block-leaves.js    |  20 ++++-
 scripts/score-geometric-block-leaves-pilot.js | 103 ++++++++++++++++++++++++++
 2 files changed, 120 insertions(+), 3 deletions(-)

diff --git a/scripts/generate-geometric-block-leaves.js b/scripts/generate-geometric-block-leaves.js
index cd325fe..a6509a3 100755
--- a/scripts/generate-geometric-block-leaves.js
+++ b/scripts/generate-geometric-block-leaves.js
@@ -11,6 +11,17 @@ const path = require('path');
 
 const ROOT = path.join(__dirname, '..');
 
+// Minimal CLI flag support: --start=N --count=M (1-indexed inclusive start)
+function arg(name, fallback) {
+  const m = process.argv.find(a => a.startsWith('--' + name + '='));
+  if (m) return m.slice(name.length + 3);
+  const i = process.argv.indexOf('--' + name);
+  if (i >= 0 && process.argv[i + 1] && !process.argv[i + 1].startsWith('--')) return process.argv[i + 1];
+  return fallback;
+}
+const START = parseInt(arg('start', '1'), 10);
+const COUNT = parseInt(arg('count', '0'), 10);
+
 const SHAPES = [
   'uniform hexagonal honeycomb tiles, edge-to-edge tessellation',
   'vertical diamond rhombi stacked in identical orientation, tightly grid-locked',
@@ -43,17 +54,20 @@ const NEGATIVE_SUFFIX =
   'Architectural, minimalist, brutalist-modern, screen-print aesthetic, single-ink woodblock print feeling. ' +
   'Archival paper texture. No text, no watermark, no signature.';
 
-console.log('Generating ' + SHAPES.length + ' geometric-block leaf designs');
+const startIdx = Math.max(0, START - 1);
+const endIdx = COUNT > 0 ? Math.min(SHAPES.length, startIdx + COUNT) : SHAPES.length;
+
+console.log('Generating ' + (endIdx - startIdx) + ' / ' + SHAPES.length + ' geometric-block leaf designs (range ' + (startIdx+1) + '..' + endIdx + ')');
 console.log('Backend: ' + (process.env.GEN_BACKEND || 'replicate') + ' | Category: geometric-block-leaves\n');
 
 let success = 0, fail = 0;
 const ids = [];
 
-for (let i = 0; i < SHAPES.length; i++) {
+for (let i = startIdx; i < endIdx; i++) {
   const shape = SHAPES[i];
   const prompt = 'Geometric block leaf pattern - ' + shape + '. ' + NEGATIVE_SUFFIX;
 
-  console.log('[' + (i+1) + '/' + SHAPES.length + '] ' + shape.slice(0, 60));
+  console.log('[' + (i+1) + '/' + endIdx + '] ' + shape.slice(0, 60));
   const r = spawnSync('node', [
     path.join(__dirname, 'generate_designs.js'),
     '--n', '1',
diff --git a/scripts/score-geometric-block-leaves-pilot.js b/scripts/score-geometric-block-leaves-pilot.js
new file mode 100644
index 0000000..792717d
--- /dev/null
+++ b/scripts/score-geometric-block-leaves-pilot.js
@@ -0,0 +1,103 @@
+#!/usr/bin/env node
+// Score the geometric-block-leaves pilot: for every row in category that was
+// created since START_TS_UNIX (set by env), capture
+//   - id
+//   - primitive (parsed from prompt)
+//   - settlement-gate verdict (text-side, deterministic)
+//   - ghost-detector verdict (analyzeGhostLayerStable, 3-call majority vote)
+//   - pass/fail (settlement=OK AND ghost.hasGhostLayer=false)
+// and write JSONL to data/geometric-block-leaves-pilot-<ISO>.jsonl plus a
+// markdown table next to it.
+
+require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
+const fs = require('fs');
+const path = require('path');
+const { spawnSync } = require('child_process');
+const { checkPrompt } = require('./settlement-gate');
+const { analyzeGhostLayerStable } = require('../lib/ghost-detector');
+
+const ROOT = path.join(__dirname, '..');
+const SINCE = process.env.PILOT_SINCE_TS;
+if (!SINCE) {
+  console.error('PILOT_SINCE_TS env var required (postgres-castable timestamp, e.g. 2026-05-26T18:00:00)');
+  process.exit(1);
+}
+
+function psql(sql) {
+  const r = spawnSync('psql', ['dw_unified', '-At', '-F', '\t', '-q'], { input: sql, encoding: 'utf8', maxBuffer: 50_000_000 });
+  if (r.status !== 0) throw new Error(r.stderr || r.stdout || 'psql failed');
+  return r.stdout;
+}
+
+function parsePrimitive(prompt) {
+  // Strip the canonical "Geometric block leaf pattern - <SHAPE>. " prefix
+  const m = String(prompt || '').match(/Geometric block leaf pattern - ([^.]+)\./);
+  return m ? m[1].trim() : null;
+}
+
+(async () => {
+  const sql = `SELECT id, prompt, local_path, image_url
+               FROM spoon_all_designs
+               WHERE category='geometric-block-leaves'
+                 AND created_at >= '${SINCE.replace(/'/g, "''")}'::timestamptz
+               ORDER BY id ASC;`;
+  const rows = psql(sql).trim().split('\n').filter(Boolean).map(line => {
+    const [id, prompt, local_path, image_url] = line.split('\t');
+    return { id: parseInt(id, 10), prompt, local_path, image_url };
+  });
+  console.log(`Found ${rows.length} pilot rows since ${SINCE}.`);
+  if (!rows.length) process.exit(0);
+
+  const results = [];
+  for (const row of rows) {
+    const primitive = parsePrimitive(row.prompt);
+    const sg = checkPrompt(row.prompt || '');
+    let ghost;
+    try {
+      ghost = await analyzeGhostLayerStable(row.local_path);
+    } catch (e) {
+      ghost = { error: e.message };
+    }
+    const pass = sg.verdict === 'OK' && ghost && ghost.hasGhostLayer === false;
+    const rec = {
+      id: row.id,
+      primitive,
+      local_path: row.local_path,
+      image_url: row.image_url,
+      settlement: {
+        verdict: sg.verdict,
+        a1: sg.partA?.a1, a2: sg.partA?.a2, a3: sg.partA?.a3,
+        partA: sg.partA?.satisfied,
+        partB: sg.partB?.satisfied,
+      },
+      ghost: ghost.error ? { error: ghost.error } : {
+        hasGhostLayer: ghost.hasGhostLayer,
+        confidence: ghost.confidence,
+        unanimous: ghost.unanimous,
+        votes: ghost.votes,
+        reason: ghost.reason,
+      },
+      pass,
+    };
+    results.push(rec);
+    const ghostStr = ghost.error ? `ERR(${ghost.error})` : `${ghost.hasGhostLayer ? 'GHOST' : 'clean'} (${ghost.confidence?.toFixed?.(2) ?? '?'}${ghost.unanimous ? ' unanimous' : ''})`;
+    console.log(`#${row.id.toString().padStart(5)} · settlement=${sg.verdict.padEnd(13)} · ghost=${ghostStr.padEnd(28)} · ${pass ? 'PASS' : 'FAIL'} · ${primitive?.slice(0, 50) || ''}`);
+  }
+
+  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
+  const outBase = path.join(ROOT, 'data', `geometric-block-leaves-pilot-${stamp}`);
+  fs.writeFileSync(outBase + '.jsonl', results.map(r => JSON.stringify(r)).join('\n') + '\n');
+
+  // markdown table
+  const lines = ['# Geometric-block-leaves pilot — re-run with stable ghost-detector', '', `Since: \`${SINCE}\`  ·  Rows: ${results.length}`, '', '| id | primitive | settlement | ghost | pass |', '|---|---|---|---|---|'];
+  for (const r of results) {
+    const g = r.ghost.error ? `ERR` : `${r.ghost.hasGhostLayer ? 'GHOST' : 'clean'} c=${(r.ghost.confidence ?? 0).toFixed(2)}${r.ghost.unanimous ? ' ✓' : ''}`;
+    lines.push(`| ${r.id} | ${(r.primitive || '').slice(0, 60)} | ${r.settlement.verdict} | ${g} | ${r.pass ? '✅' : '❌'} |`);
+  }
+  const passCount = results.filter(r => r.pass).length;
+  lines.push('', `**Tally:** ${passCount}/${results.length} PASS  ·  ${results.length - passCount} FAIL`);
+  fs.writeFileSync(outBase + '.md', lines.join('\n') + '\n');
+
+  console.log(`\nWrote ${outBase}.jsonl + .md`);
+  console.log(`Final: ${passCount}/${results.length} PASS`);
+})();

← e69a345 gitignore: exclude data/edges-scan-report-*.md (regeneratabl  ·  back to Wallco Ai  ·  pm2: ecosystem.wallco-ai-9878.config.js with restart-loop gu 4f3993d →