← back to Dw Settlement Audit
report.js
130 lines
#!/usr/bin/env node
/* DW settlement audit — report generator.
*
* Reads verdicts.jsonl and produces:
* - report.md : human-readable sign-off report, PROHIBITED + NEEDS_REVIEW first
* - report.html : same, with thumbnails + Shopify admin deep-links
*
* Report-only. This is a SCREENING TRIAGE, not a legal adjudication. Nothing is
* unpublished or deleted — that is Steve's call after review.
*
* node report.js
*/
'use strict';
const fs = require('fs');
const SHOP_ADMIN = 'https://admin.shopify.com/store/designer-laboratory-sandbox';
const VERD = 'verdicts.jsonl';
const lines = (() => {
try { return fs.readFileSync(VERD, 'utf8').split('\n').filter(Boolean); }
catch { console.error('no verdicts.jsonl — run verify-candidates.js first'); process.exit(1); }
})();
const recs = lines.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
let manifest = {};
try { manifest = JSON.parse(fs.readFileSync('run-manifest.json', 'utf8')); } catch {}
const by = v => recs.filter(r => r.verdict === v);
const prohibited = by('PROHIBITED');
const needsReview = by('NEEDS_REVIEW');
const permitted = by('PERMITTED');
const noImage = by('NO_IMAGE');
const errors = by('ERROR');
const adminUrl = id => `${SHOP_ADMIN}/products/${id}`;
const els = r => (r.partBElements && r.partBElements.length) ? r.partBElements.join(', ') : '—';
const esc = s => String(s == null ? '' : s)
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
.replace(/"/g, '"');
const DISCLAIMER = [
'**This is a screening triage, not a legal adjudication.** Verdicts come from Gemini',
'vision against the verbatim settlement binding text — they flag candidates for human',
'review, they do not decide settlement compliance.',
'',
'- **NEEDS REVIEW items are currently PERMITTED** under the settlement until a human',
' re-evaluates them. The settlement has two legal states (PROHIBITED / PERMITTED);',
' NEEDS REVIEW is a review-queue bucket, not a third legal state.',
'- **Primary image only.** Only each product\'s first image was checked. Multi-image',
' products (see `images: N`) may hide a Part B element in image 2+ — re-check by hand.',
'- **Report-only.** No product has been unpublished, edited, or deleted.',
].join('\n');
// ---- markdown ----
let md = `# DW Settlement Audit — Verdict Report\n\n`;
md += `Generated ${new Date().toISOString()} · ${recs.length} candidates verified\n`;
if (manifest.bindingSha256) md += `Binding vault SHA256: \`${manifest.bindingSha256}\`\n`;
md += `\n| Verdict | Count |\n|---|---|\n`;
md += `| 🔴 PROHIBITED | ${prohibited.length} |\n`;
md += `| 🟡 NEEDS REVIEW | ${needsReview.length} |\n`;
md += `| 🟢 PERMITTED | ${permitted.length} |\n`;
md += `| ⚪ NO_IMAGE | ${noImage.length} |\n`;
md += `| ⚠️ ERROR | ${errors.length} |\n\n`;
md += DISCLAIMER + `\n\n`;
function mdSection(title, arr) {
if (!arr.length) return '';
let s = `## ${title} (${arr.length})\n\n`;
for (const r of arr) {
s += `### ${r.title || r.handle}\n`;
s += `- Product ID: \`${r.id}\` · handle: \`${r.handle}\` · status: ${r.status}, published: ${r.published}\n`;
s += `- Part A: ${r.partA} (a1=${r.a1} a2=${r.a2} a3=${r.a3}) · Part B: ${r.partB} · elements: ${els(r)}`;
s += ` · acceptable: ${r.acceptable}${r.uncertain ? ' · UNCERTAIN' : ''}\n`;
s += `- Stage-1 keyword hit: \`${r.match}\` · images on product: ${r.imageCount != null ? r.imageCount : '?'}\n`;
s += `- Reason: ${r.reason || '—'}\n`;
s += `- Admin: ${adminUrl(r.id)}\n`;
if (r.imageUrl) s += `- Image: ${r.imageUrl}\n`;
s += `\n`;
}
return s;
}
md += mdSection('🔴 PROHIBITED — recommend unpublish pending review', prohibited);
md += mdSection('🟡 NEEDS REVIEW — currently permitted, needs Steve\'s eyes', needsReview);
if (errors.length) md += mdSection('⚠️ ERROR — re-run verify for these', errors);
fs.writeFileSync('report.md', md);
// ---- html ----
function card(r) {
const c = r.verdict === 'PROHIBITED' ? '#b3261e' : r.verdict === 'NEEDS_REVIEW' ? '#b58a00' : '#888';
const multi = r.imageCount > 1 ? ` · <b>${r.imageCount} images — primary only checked</b>` : '';
return `<div class="card" style="border-left:5px solid ${c}">
${r.imageUrl ? `<img src="${esc(r.imageUrl)}" loading="lazy">` : '<div class="noimg">no image</div>'}
<div class="meta">
<span class="badge" style="background:${c}">${esc(r.verdict)}</span>
<h3>${esc(r.title || r.handle)}</h3>
<p class="sub">ID ${esc(r.id)} · ${esc(r.status)}${r.published ? ' · published' : ''} · keyword: ${esc(r.match)}${multi}</p>
<p class="gate">Part A: <b>${r.partA}</b> (a1 ${r.a1} / a2 ${r.a2} / a3 ${r.a3}) · Part B: <b>${r.partB}</b> · ${esc(els(r))} · acceptable: ${r.acceptable}${r.uncertain ? ' · <b>UNCERTAIN</b>' : ''}</p>
<p class="reason">${esc(r.reason)}</p>
<a href="${esc(adminUrl(r.id))}" target="_blank" rel="noopener noreferrer">Open in Shopify admin →</a>
</div></div>`;
}
const flagged = [...prohibited, ...needsReview];
const html = `<!doctype html><meta charset=utf-8><title>DW Settlement Audit</title>
<style>
body{font:15px/1.5 -apple-system,system-ui,sans-serif;background:#faf8f4;color:#1a1710;margin:0;padding:32px}
h1{margin:0 0 4px}.summary{color:#555;margin-bottom:10px}
.summary b{color:#b3261e}
.note{background:#fff8e1;border:1px solid #e8d9a0;border-radius:6px;padding:10px 14px;font-size:13px;color:#5a4a1a;margin-bottom:22px}
.card{display:flex;gap:16px;background:#fff;border-radius:8px;padding:14px;margin-bottom:14px;box-shadow:0 1px 4px #0001}
.card img{width:160px;height:160px;object-fit:cover;object-position:center 12%;border-radius:6px;flex:none}
.noimg{width:160px;height:160px;background:#eee;display:flex;align-items:center;justify-content:center;color:#999;border-radius:6px;flex:none}
.meta{flex:1;min-width:0}.meta h3{margin:4px 0}
.badge{color:#fff;font-size:11px;font-weight:700;padding:2px 8px;border-radius:4px}
.sub{color:#777;font-size:13px;margin:2px 0}.gate{font-size:13px;margin:4px 0}
.reason{font-style:italic;color:#444;margin:4px 0}
a{color:#0b6}
</style>
<h1>DW Settlement Audit — Verdict Report</h1>
<p class="summary">Generated ${new Date().toISOString()} · ${recs.length} verified ·
<b>${prohibited.length} PROHIBITED</b> · ${needsReview.length} NEEDS REVIEW ·
${permitted.length} PERMITTED · ${errors.length} errors.</p>
<div class="note"><b>Screening triage, not a legal adjudication.</b>
NEEDS REVIEW items are currently PERMITTED until a human re-evaluates them.
Primary product image only — multi-image products may hide a Part B element in image 2+.
Report-only: no product has been changed.${manifest.bindingSha256 ? ` Binding vault SHA256: ${esc(manifest.bindingSha256)}.` : ''}</div>
${flagged.length ? flagged.map(card).join('\n') : '<p>No PROHIBITED or NEEDS REVIEW designs found.</p>'}`;
fs.writeFileSync('report.html', html);
console.log(`report.md + report.html written — ${prohibited.length} PROHIBITED, ${needsReview.length} NEEDS_REVIEW, ${errors.length} ERROR (of ${recs.length})`);