[object Object]

← back to Wallco Ai

Add settlement-gate-batch.js: gate ungated designs by category, stamp verdict

4eebff7267387f0d3c8073a8a3e85a220874d734 · 2026-06-02 10:47:20 -0700 · Steve Abrams

Used to gate 517 round-1 damasks (513 OK) before publishing live.
Excludes heal-derivatives (midheal_/edgeheal_/smartfix_).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 4eebff7267387f0d3c8073a8a3e85a220874d734
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jun 2 10:47:20 2026 -0700

    Add settlement-gate-batch.js: gate ungated designs by category, stamp verdict
    
    Used to gate 517 round-1 damasks (513 OK) before publishing live.
    Excludes heal-derivatives (midheal_/edgeheal_/smartfix_).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 scripts/settlement-gate-batch.js | 70 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 70 insertions(+)

diff --git a/scripts/settlement-gate-batch.js b/scripts/settlement-gate-batch.js
new file mode 100644
index 0000000..6de67b1
--- /dev/null
+++ b/scripts/settlement-gate-batch.js
@@ -0,0 +1,70 @@
+#!/usr/bin/env node
+/**
+ * settlement-gate-batch.js — run the settlement post-gen vision gate over a
+ * batch of un-gated designs and stamp each verdict onto all_designs.
+ *
+ * Used to clear the 2026-06-02 damask backlog: ~1764 damask rows that were
+ * generated before the gate was wired and so carry settlement_verdict=NULL.
+ * Only round-1 originals are eligible (heal-derivatives are excluded, matching
+ * the curator). Never publishes — stamping the verdict is all this does; a
+ * separate, explicit publish step flips is_published for the OK ones.
+ *
+ * Usage:
+ *   node scripts/settlement-gate-batch.js --category damask --limit 100
+ *   node scripts/settlement-gate-batch.js --category damask --all --concurrency 4
+ */
+const path = require('path');
+const fs = require('fs');
+const { spawnSync, execSync } = require('child_process');
+require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
+
+const ROOT = path.join(__dirname, '..');
+const arg = (k, d) => { const m = process.argv.find(a => a.startsWith('--' + k + '=')); if (m) return m.split('=')[1]; const i = process.argv.indexOf('--' + k); return (i >= 0 && process.argv[i + 1] && !process.argv[i + 1].startsWith('--')) ? process.argv[i + 1] : d; };
+const has = k => process.argv.includes('--' + k);
+const CATEGORY = arg('category', 'damask');
+const LIMIT = has('all') ? null : parseInt(arg('limit', '100'), 10);
+const CONC = parseInt(arg('concurrency', '4'), 10);
+
+const psql = sql => execSync(`psql -d dw_unified -tAc "${sql.replace(/"/g, '\\"')}"`, { encoding: 'utf8' }).trim();
+
+function gateOne(id, localPath) {
+  const r = spawnSync('node', [path.join(__dirname, 'settlement_postgen_vision_check.js'), localPath, CATEGORY],
+    { cwd: ROOT, encoding: 'utf8', timeout: 90_000, env: process.env });
+  const out = `${r.stdout || ''}${r.stderr || ''}`;
+  const m = out.match(/VERDICT:\s*(BLOCK|NEEDS REVIEW|OK)/);
+  let verdict;
+  if (r.status === 2 || (m && m[1] === 'BLOCK')) verdict = 'BLOCK';
+  else if (m) verdict = m[1] === 'NEEDS REVIEW' ? 'NEEDS_REVIEW' : 'OK';
+  else verdict = 'REVIEW-VISION-ERROR';        // fail-closed
+  psql(`UPDATE all_designs SET settlement_verdict='${verdict}' WHERE id=${id}`);
+  return verdict;
+}
+
+(async () => {
+  const where = `category='${CATEGORY}' AND settlement_verdict IS NULL `
+    + `AND COALESCE(user_removed,false)=false AND local_path IS NOT NULL `
+    + `AND local_path NOT LIKE '%midheal_%' AND local_path NOT LIKE '%edgeheal_%' AND local_path NOT LIKE '%smartfix_%'`;
+  const rows = psql(`SELECT id||'|'||local_path FROM all_designs WHERE ${where} ORDER BY random()${LIMIT ? ' LIMIT ' + LIMIT : ''}`)
+    .split('\n').filter(Boolean).map(l => { const [id, ...rest] = l.split('|'); return { id: parseInt(id, 10), lp: rest.join('|') }; })
+    .filter(r => { try { return fs.existsSync(r.lp); } catch { return false; } });
+
+  console.log(`Gating ${rows.length} un-gated ${CATEGORY} (concurrency ${CONC})...`);
+  const counts = {}; const examples = {};
+  let done = 0;
+  for (let i = 0; i < rows.length; i += CONC) {
+    const slice = rows.slice(i, i + CONC);
+    const results = await Promise.all(slice.map(r => new Promise(res => {
+      try { res({ id: r.id, v: gateOne(r.id, r.lp) }); } catch (e) { res({ id: r.id, v: 'REVIEW-VISION-ERROR' }); }
+    })));
+    for (const { id, v } of results) {
+      counts[v] = (counts[v] || 0) + 1;
+      (examples[v] = examples[v] || []).push(id);
+    }
+    done += slice.length;
+    if (done % 20 === 0 || done === rows.length) process.stdout.write(`  ${done}/${rows.length}\r`);
+  }
+  console.log(`\n\n=== verdict distribution (${rows.length} gated) ===`);
+  for (const [v, n] of Object.entries(counts).sort((a, b) => b[1] - a[1])) {
+    console.log(`  ${v.padEnd(20)} ${n}\t e.g. ${(examples[v] || []).slice(0, 5).join(', ')}`);
+  }
+})();

← 4c1e3e3 deploy: add needs-tif admin-asset smoke test (5c) — assert m  ·  back to Wallco Ai  ·  new.html: add Sort (6 opts incl color-by-hue) + Density slid 7aa9645 →