[object Object]

← back to Dw Settlement Audit

settlement audit stage 2: Gemini vision verifier (resumable, no auto-delete)

e577f6fe91e29a9c0b14f16339b50f91c700e4fd · 2026-05-18 17:30:00 -0700 · SteveStudio2

Files touched

Diff

commit e577f6fe91e29a9c0b14f16339b50f91c700e4fd
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Mon May 18 17:30:00 2026 -0700

    settlement audit stage 2: Gemini vision verifier (resumable, no auto-delete)
---
 verify-candidates.js | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 117 insertions(+)

diff --git a/verify-candidates.js b/verify-candidates.js
new file mode 100644
index 0000000..bc8a4d6
--- /dev/null
+++ b/verify-candidates.js
@@ -0,0 +1,117 @@
+#!/usr/bin/env node
+/* DW settlement audit — stage 2: vision verify.
+ *
+ * Reads candidates.jsonl (stage-1 keyword hits), fetches each product's primary
+ * image, and runs it through Gemini vision against the verbatim settlement gate.
+ * Writes verdicts.jsonl. Resumable — skips products already in verdicts.jsonl.
+ *
+ * NEVER deletes or unpublishes anything. Output is a report for Steve's sign-off.
+ *
+ *   SHOPIFY_ADMIN_TOKEN=... GEMINI_API_KEY=... node verify-candidates.js
+ */
+'use strict';
+const fs = require('fs');
+
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const STOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const GKEY = process.env.GEMINI_API_KEY;
+if (!STOKEN || !GKEY) { console.error('need SHOPIFY_ADMIN_TOKEN + GEMINI_API_KEY'); process.exit(1); }
+
+const CAND = 'candidates.jsonl';
+const VERD = 'verdicts.jsonl';
+const PROG = 'progress2.txt';
+
+// Verbatim settlement gate — combined Part A + Part B test.
+const GATE = `You are auditing a wallcovering product image against a binding legal settlement.
+
+PART A — a design satisfies Part A only if ALL THREE are present together:
+  A1. Repeating pattern with directional variation amongst leaves, palm fronds, or similar foliage.
+  A2. Open space between the leaves.
+  A3. More than one ink/color.
+
+PART B — a design satisfies Part B if ANY ONE appears:
+  Bananas or banana pods; grapes; birds (any species); butterflies (any species).
+
+The design is PROHIBITED if and only if BOTH Part A is fully satisfied AND Part B is satisfied.
+A design satisfying Part A but with NO Part B element is PERMITTED.
+A Part B element on a non-leaf substrate (single-color trellis, cane lattice, geometric,
+mural) is PERMITTED because Part A is not fully satisfied.
+Use the defendant-favorable reading. If genuinely borderline, return "BORDERLINE".
+
+Reply with ONLY a compact JSON object, no prose, no markdown fence:
+{"a1":bool,"a2":bool,"a3":bool,"partA":bool,"partB":bool,"verdict":"PROHIBITED|PERMITTED|BORDERLINE","reason":"<=25 words"}`;
+
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+const readLines = f => { try { return fs.readFileSync(f, 'utf8').split('\n').filter(Boolean); } catch { return []; } };
+
+async function shopify(path) {
+  for (let a = 0; a < 5; a++) {
+    const r = await fetch(`https://${SHOP}/admin/api/2024-01/${path}`,
+      { headers: { 'X-Shopify-Access-Token': STOKEN } });
+    if (r.status === 429) { await sleep(2000); continue; }
+    if (!r.ok) return null;
+    return r.json();
+  }
+  return null;
+}
+
+async function geminiVerify(imgB64, mime, ctx) {
+  const body = {
+    contents: [{ parts: [
+      { text: GATE + '\n\nProduct title: ' + ctx },
+      { inline_data: { mime_type: mime, data: imgB64 } },
+    ] }],
+    generationConfig: { temperature: 0, maxOutputTokens: 300 },
+  };
+  for (let a = 0; a < 4; a++) {
+    const r = await fetch(
+      'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=' + GKEY,
+      { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
+    if (r.status === 429 || r.status >= 500) { await sleep(3000); continue; }
+    const j = await r.json();
+    const txt = j?.candidates?.[0]?.content?.parts?.[0]?.text || '';
+    const m = txt.match(/\{[\s\S]*\}/);
+    if (m) { try { return JSON.parse(m[0]); } catch {} }
+    return { verdict: 'ERROR', reason: 'unparseable: ' + txt.slice(0, 80) };
+  }
+  return { verdict: 'ERROR', reason: 'gemini unavailable' };
+}
+
+(async () => {
+  const cands = readLines(CAND).map(l => JSON.parse(l));
+  const done = new Set(readLines(VERD).map(l => { try { return JSON.parse(l).id; } catch { return null; } }));
+  const todo = cands.filter(c => !done.has(c.id));
+  let n = 0, prohibited = 0, borderline = 0, err = 0;
+  const out = fs.createWriteStream(VERD, { flags: 'a' });
+
+  for (const c of todo) {
+    n++;
+    let rec = { id: c.id, handle: c.handle, title: c.title, status: c.status,
+      published: c.published, match: c.match };
+    try {
+      const imgs = await shopify(`products/${c.id}/images.json`);
+      const src = imgs?.images?.[0]?.src;
+      if (!src) { rec.verdict = 'NO_IMAGE'; rec.reason = 'product has no image'; }
+      else {
+        const ir = await fetch(src);
+        const buf = Buffer.from(await ir.arrayBuffer());
+        const mime = ir.headers.get('content-type') || 'image/jpeg';
+        const v = await geminiVerify(buf.toString('base64'), mime.split(';')[0], c.title || '');
+        rec = Object.assign(rec, v, { imageUrl: src });
+      }
+    } catch (e) { rec.verdict = 'ERROR'; rec.reason = e.message; }
+    if (rec.verdict === 'PROHIBITED') prohibited++;
+    if (rec.verdict === 'BORDERLINE') borderline++;
+    if (rec.verdict === 'ERROR') err++;
+    out.write(JSON.stringify(rec) + '\n');
+    if (n % 10 === 0 || n === todo.length) {
+      fs.writeFileSync(PROG, `verified ${n}/${todo.length} (of ${cands.length} total) | ` +
+        `PROHIBITED ${prohibited} | BORDERLINE ${borderline} | ERROR ${err} | ${new Date().toISOString()}\n`);
+    }
+    await sleep(700); // pace Shopify + Gemini
+  }
+  out.end();
+  fs.writeFileSync(PROG, `DONE | verified ${n}/${todo.length} | PROHIBITED ${prohibited} | ` +
+    `BORDERLINE ${borderline} | ERROR ${err} | ${new Date().toISOString()}\n`);
+  console.log(`stage 2 done — ${n} verified, ${prohibited} prohibited, ${borderline} borderline, ${err} errors`);
+})();

← 2fca7eb initial scaffold — DW settlement catalog audit, stage 1 text  ·  back to Dw Settlement Audit  ·  stage 2: disable Gemini thinking budget — was truncating ver a3e513d →