← back to Wallco Ai
scripts/bulk-reject-auto-smart-fix.js
85 lines
#!/usr/bin/env node
// bulk-reject-auto-smart-fix — one-time cleanup of overnight seamless-agent output.
//
// The seamless-agent ran 12+ hours and auto-fired smart-fix on every flagged
// design. Steve rejected the OUTPUT QUALITY ("amateur"). This script:
// 1. Finds every smart-fix event from data/fixes-feed.jsonl in the last N hours
// 2. For each: unpublishes the new_id, republishes the parent (source)
// 3. Tags the new_id with 'etsy-digital-file' (kept for digital-art repurposing)
// 4. Logs every decision to data/fix-decisions.jsonl
//
// Usage:
// node scripts/bulk-reject-auto-smart-fix.js # dry-run, last 12h
// node scripts/bulk-reject-auto-smart-fix.js --apply # execute
// node scripts/bulk-reject-auto-smart-fix.js --apply --hours 24
// node scripts/bulk-reject-auto-smart-fix.js --apply --kinds smart-fix,regenerate-reverse
'use strict';
const fs = require('fs');
const path = require('path');
const { psqlExecLocal } = require('../lib/db.js');
const ARGS = process.argv.slice(2);
const arg = (n, d) => { const i = ARGS.indexOf('--' + n); return i >= 0 ? ARGS[i+1] : d; };
const APPLY = ARGS.includes('--apply');
const HOURS = parseInt(arg('hours', '12'), 10);
const KINDS = (arg('kinds', 'smart-fix') || 'smart-fix').split(',').map(s => s.trim());
const ROOT = path.join(__dirname, '..');
const FEED = path.join(ROOT, 'data', 'fixes-feed.jsonl');
const DECISIONS= path.join(ROOT, 'data', 'fix-decisions.jsonl');
function log(m) { console.log(`[${new Date().toISOString()}] ${m}`); }
if (!fs.existsSync(FEED)) { log('no fixes-feed.jsonl — nothing to do'); process.exit(0); }
const cutoffMs = Date.now() - HOURS * 3600 * 1000;
const lines = fs.readFileSync(FEED, 'utf8').split('\n').filter(Boolean);
const targets = [];
for (const line of lines) {
let row; try { row = JSON.parse(line); } catch { continue; }
if (!KINDS.includes(row.kind)) continue;
const ts = row.ts ? Date.parse(row.ts) : 0;
if (ts < cutoffMs) continue;
if (!row.new_id || !row.src_id) continue;
targets.push(row);
}
log(`mode=${APPLY ? 'APPLY' : 'DRY-RUN'} · kinds=${KINDS.join(',')} · hours=${HOURS} · candidates=${targets.length}`);
if (!targets.length) { log('nothing to reject'); process.exit(0); }
if (!APPLY) {
log('sample (first 5):');
for (const r of targets.slice(0, 5)) log(` ${r.kind} #${r.src_id} → #${r.new_id} (${r.ts})`);
log('re-run with --apply to execute');
process.exit(0);
}
let ok = 0, errs = 0;
for (const r of targets) {
try {
// 1. unpublish new + mark for etsy-digital-file repurposing
// PG quirk: `text[] || 'literal'` triggers "malformed array literal"
// when the right side isn't explicitly cast as text[]. Use ARRAY[]::text[].
psqlExecLocal(`UPDATE spoon_all_designs
SET is_published=FALSE,
tags = array_remove(COALESCE(tags, ARRAY[]::text[]), 'etsy-digital-file') || ARRAY['etsy-digital-file']::text[]
WHERE id=${r.new_id};`);
// 2. republish parent
psqlExecLocal(`UPDATE spoon_all_designs SET is_published=TRUE WHERE id=${r.src_id};`);
// 3. audit log
fs.appendFileSync(DECISIONS, JSON.stringify({
ts: new Date().toISOString(), new_id: r.new_id, parent: r.src_id,
verdict: 'reject_bulk_auto_smart_fix',
tagged_as: 'etsy-digital-file',
source_event: r,
}) + '\n');
ok++;
} catch (e) {
errs++;
log(` ERR #${r.new_id}: ${e.message.slice(0, 140)}`);
}
if ((ok + errs) % 25 === 0) log(`progress ${ok+errs}/${targets.length} · ok=${ok} err=${errs}`);
}
log(`done · ${ok} rejected + tagged etsy-digital-file · ${errs} errors · log=${DECISIONS}`);