← back to Designer Wallcoverings
add color-clean-report.js — per-color rollup (processed/removed/banners), top banner vendors to re-crawl, most-removed tags, est Gemini cost, rollback hint
df20fe8c9a7d2ad92924c65ff8ef5a6fe30aacca · 2026-06-12 14:02:54 -0700 · Steve Abrams
Files touched
A DW-Programming/color-clean-report.js
Diff
commit df20fe8c9a7d2ad92924c65ff8ef5a6fe30aacca
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Jun 12 14:02:54 2026 -0700
add color-clean-report.js — per-color rollup (processed/removed/banners), top banner vendors to re-crawl, most-removed tags, est Gemini cost, rollback hint
---
DW-Programming/color-clean-report.js | 59 ++++++++++++++++++++++++++++++++++++
1 file changed, 59 insertions(+)
diff --git a/DW-Programming/color-clean-report.js b/DW-Programming/color-clean-report.js
new file mode 100644
index 00000000..7406201e
--- /dev/null
+++ b/DW-Programming/color-clean-report.js
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+// Rollup report for the color-collection cleaner.
+// Reads the ledger + banner re-crawl queue + per-color .done state and prints a
+// per-color summary, top banner vendors, and an estimated Gemini 2.5-flash cost.
+// Usage: node color-clean-report.js
+const fs = require('fs');
+const path = require('path');
+const DATA = path.join(__dirname, 'data');
+const LEDGER = path.join(DATA, 'color-clean-ledger.jsonl');
+const BANNER = path.join(DATA, 'banner-recrawl-queue.jsonl');
+const STATE = path.join(DATA, 'color-clean-state');
+
+// Per-call cost estimate for gemini-2.5-flash with one inline image + ~60 out tokens.
+// image ≈ 258 input tokens (low-res tile) + ~120 prompt tokens; in $0.30/1M, out $2.50/1M.
+const COST_PER_CALL = (378 * 0.30 + 60 * 2.50) / 1e6; // ≈ $0.00026
+
+const readJsonl = (f) => { try { return fs.readFileSync(f, 'utf8').split('\n').filter(Boolean).map(JSON.parse); } catch { return []; } };
+
+const ledger = readJsonl(LEDGER).filter(e => !e.dry_run); // real removals only
+const banners = readJsonl(BANNER);
+const doneCounts = {};
+try { for (const f of fs.readdirSync(STATE)) if (f.endsWith('.done')) doneCounts[f.replace('.done', '')] = fs.readFileSync(path.join(STATE, f), 'utf8').split('\n').filter(Boolean).length; } catch {}
+
+const byColor = (arr) => arr.reduce((m, e) => ((m[e.color] = (m[e.color] || 0) + 1), m), {});
+const rmByColor = byColor(ledger);
+const bnByColor = byColor(banners);
+
+console.log('\n=== COLOR-COLLECTION CLEANER — REPORT ===\n');
+const colors = [...new Set([...Object.keys(doneCounts), ...Object.keys(rmByColor), ...Object.keys(bnByColor)])].sort();
+console.log(' color processed removed banners');
+console.log(' ' + '-'.repeat(40));
+let tProc = 0, tRm = 0, tBn = 0;
+for (const c of colors) {
+ const p = doneCounts[c] || 0, r = rmByColor[c] || 0, b = bnByColor[c] || 0;
+ tProc += p; tRm += r; tBn += b;
+ console.log(` ${c.padEnd(8)} ${String(p).padStart(8)} ${String(r).padStart(9)} ${String(b).padStart(9)}`);
+}
+console.log(' ' + '-'.repeat(40));
+console.log(` ${'TOTAL'.padEnd(8)} ${String(tProc).padStart(8)} ${String(tRm).padStart(9)} ${String(tBn).padStart(9)}`);
+
+// Top banner vendors (these products have burned-in watermarks/swatch-strips → re-crawl)
+const vend = banners.reduce((m, e) => ((m[e.vendor || '?'] = (m[e.vendor || '?'] || 0) + 1), m), {});
+const topVend = Object.entries(vend).sort((a, b) => b[1] - a[1]).slice(0, 10);
+if (topVend.length) {
+ console.log('\n Top banner-flagged vendors (need re-crawl):');
+ for (const [v, n] of topVend) console.log(` ${String(n).padStart(4)} ${v}`);
+}
+
+// Most-removed tags
+const tagFreq = ledger.flatMap(e => e.removed_tags || []).reduce((m, t) => ((m[t] = (m[t] || 0) + 1), m), {});
+const topTags = Object.entries(tagFreq).sort((a, b) => b[1] - a[1]).slice(0, 12);
+if (topTags.length) {
+ console.log('\n Most-removed color tags:');
+ console.log(' ' + topTags.map(([t, n]) => `${t}:${n}`).join(' '));
+}
+
+const estCost = tProc * COST_PER_CALL;
+console.log(`\n Est. Gemini cost: ~$${estCost.toFixed(2)} (${tProc} calls × ~$${COST_PER_CALL.toFixed(5)})`);
+console.log(` Rollback any time: node color-collection-cleaner-v2.js --rollback ${path.relative(process.cwd(), LEDGER)}\n`);
← 5672b1c4 color cleaner v2: store-agnostic collection resolver — verif
·
back to Designer Wallcoverings
·
make cleaner importable (require.main guard + module.exports 88eb7001 →