[object Object]

← back to Wallco Ai

Add style-aware-sample-verify.js — settlement + edges-scan + TIF archive for sample designs

fdb94645161b822a439a6f7fc71f834d08908e88 · 2026-05-26 15:41:30 -0700 · Steve

Files touched

Diff

commit fdb94645161b822a439a6f7fc71f834d08908e88
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue May 26 15:41:30 2026 -0700

    Add style-aware-sample-verify.js — settlement + edges-scan + TIF archive for sample designs
---
 scripts/style-aware-sample-verify.js | 133 +++++++++++++++++++++++++++++++++++
 1 file changed, 133 insertions(+)

diff --git a/scripts/style-aware-sample-verify.js b/scripts/style-aware-sample-verify.js
new file mode 100644
index 0000000..c2dfd48
--- /dev/null
+++ b/scripts/style-aware-sample-verify.js
@@ -0,0 +1,133 @@
+#!/usr/bin/env node
+/**
+ * style-aware-sample-verify.js — post-generation verification + TIF archive for
+ * the style-aware SAMPLE-GATE designs (YOLO task 13).
+ *
+ * For each design id (passed on argv, or auto-discovered as the chinoiserie
+ * rows created in the last N minutes), runs:
+ *   1. settlement post-gen vision check (scripts/settlement_postgen_vision_check.js)
+ *   2. edges/seam 6-lens scan (scripts/edges-scan.py --path)
+ *   3. TIF archive at 150-DPI metadata into data/tif/<id>.tif (full-size rule)
+ * and prints a per-design PASS/WARN/FAIL summary + realized PASS rate.
+ *
+ * Does NOT flip is_published. Does NOT publish/deploy/push. Read + archive only.
+ *
+ * Usage:
+ *   node scripts/style-aware-sample-verify.js 53700 53701 ...
+ *   node scripts/style-aware-sample-verify.js --since 120   # rows in last 120 min
+ */
+require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
+const fs = require('fs');
+const path = require('path');
+const { spawnSync } = require('child_process');
+
+const ROOT = path.join(__dirname, '..');
+const TIF_DIR = path.join(ROOT, 'data', 'tif');
+fs.mkdirSync(TIF_DIR, { recursive: true });
+
+function psql(sql) {
+  const r = spawnSync('psql', ['dw_unified', '-At', '-q', '-F', '\t'], { input: sql, encoding: 'utf8' });
+  if (r.status !== 0) throw new Error(r.stderr || r.stdout || 'psql failed');
+  return r.stdout.trim();
+}
+
+function args() {
+  const a = process.argv.slice(2);
+  const ids = [];
+  let since = null;
+  for (let i = 0; i < a.length; i++) {
+    if (a[i] === '--since') since = parseInt(a[++i], 10);
+    else if (/^\d+$/.test(a[i])) ids.push(parseInt(a[i], 10));
+  }
+  return { ids, since };
+}
+
+function discoverIds(sinceMin) {
+  const rows = psql(`
+    SELECT id FROM spoon_all_designs
+    WHERE category='chinoiserie'
+      AND created_at > now() - interval '${sinceMin} minutes'
+      AND generator='comfy'
+    ORDER BY id ASC;
+  `);
+  return rows.split('\n').filter(Boolean).map(Number);
+}
+
+function designRow(id) {
+  const r = psql(`SELECT local_path, dominant_hex, palette::text, width_in, is_published FROM spoon_all_designs WHERE id=${id};`);
+  if (!r) return null;
+  const [local_path, dominant_hex, palette, width_in, is_published] = r.split('\t');
+  return { local_path, dominant_hex, palette, width_in, is_published };
+}
+
+// settlement gate
+function runSettlement(png, title) {
+  const r = spawnSync('node', [path.join(__dirname, 'settlement_postgen_vision_check.js'), png, title], { encoding: 'utf8', timeout: 60000 });
+  const out = (r.stdout || '') + (r.stderr || '');
+  // Look for a verdict token in the JSON output.
+  let verdict = 'UNKNOWN';
+  const m = out.match(/"verdict"\s*:\s*"([^"]+)"/i) || out.match(/\b(OK|BLOCK|NEEDS[ _-]?REVIEW)\b/i);
+  if (m) verdict = m[1].toUpperCase().replace(/[ _-]/g, '_');
+  return { verdict, raw: out.slice(-400) };
+}
+
+// edges/seam scan
+function runEdges(png) {
+  const r = spawnSync('python3', [path.join(__dirname, 'edges-scan.py'), '--path', png], { encoding: 'utf8', timeout: 60000 });
+  try {
+    const j = JSON.parse(r.stdout.trim());
+    return { verdict: j.verdict, scores: Object.fromEntries(Object.entries(j.lenses || j).filter(([k, v]) => v && typeof v === 'object' && 'score' in v).map(([k, v]) => [k, v.score])) };
+  } catch {
+    return { verdict: 'ERR', raw: (r.stdout || r.stderr || '').slice(-200) };
+  }
+}
+
+// TIF archive — native resolution + 150-DPI metadata (full-size archive rule)
+function buildTif(png, id) {
+  const tifPath = path.join(TIF_DIR, `sample-${id}.tif`);
+  const py = `
+from PIL import Image
+import sys
+src, out = sys.argv[1], sys.argv[2]
+im = Image.open(src).convert('RGB')
+im.save(out, 'TIFF', dpi=(150,150), compression='tiff_lzw')
+print(out)
+`;
+  const r = spawnSync('python3', ['-c', py, png, tifPath], { encoding: 'utf8', timeout: 60000 });
+  if (r.status !== 0) return { ok: false, err: (r.stderr || '').slice(-200) };
+  const bytes = fs.existsSync(tifPath) ? fs.statSync(tifPath).size : 0;
+  return { ok: bytes > 0, path: tifPath, bytes };
+}
+
+function main() {
+  const { ids: argIds, since } = args();
+  const ids = argIds.length ? argIds : discoverIds(since || 180);
+  if (!ids.length) { console.log('No design ids to verify.'); return; }
+
+  console.log(`\n=== style-aware sample VERIFY · ${ids.length} designs ===\n`);
+  const results = [];
+  for (const id of ids) {
+    const row = designRow(id);
+    if (!row || !row.local_path || !fs.existsSync(row.local_path)) {
+      console.log(`#${id}: MISSING local_path (${row && row.local_path})`);
+      results.push({ id, settlement: 'NA', edges: 'NA', tif: false });
+      continue;
+    }
+    const png = row.local_path;
+    const settlement = runSettlement(png, `Chinoiserie sample #${id}`);
+    const edges = runEdges(png);
+    const tif = buildTif(png, id);
+    console.log(`#${id}  is_published=${row.is_published}  dom=${row.dominant_hex}`);
+    console.log(`   settlement: ${settlement.verdict}`);
+    console.log(`   edges/seam: ${edges.verdict}${edges.scores ? '  ' + JSON.stringify(edges.scores) : ''}`);
+    console.log(`   tif: ${tif.ok ? tif.path + ' (' + (tif.bytes / 1024 / 1024).toFixed(2) + ' MB)' : 'FAILED ' + (tif.err || '')}`);
+    results.push({ id, settlement: settlement.verdict, edges: edges.verdict, tif: tif.ok });
+  }
+
+  // PASS = settlement OK AND edges PASS/WARN (WARN is acceptable per edges-agent thresholds)
+  const pass = results.filter(r => r.settlement === 'OK' && (r.edges === 'PASS' || r.edges === 'WARN') && r.tif).length;
+  console.log(`\n=== realized PASS rate: ${pass}/${results.length} (${results.length ? Math.round(pass / results.length * 100) : 0}%) ===`);
+  console.log('PASS criterion: settlement OK + edges PASS|WARN + TIF archived. Designs already cleared the in-pipeline ghost/seamless/frame gates at generation (only passing designs were inserted).');
+}
+
+main();

← 8d5f8a8 dogs curator: /admin/dogs-curator hot-or-not surface + /api/  ·  back to Wallco Ai  ·  promote_edge_heal: dry-run-default tool, re-scans + flips on dcdbbed →