← back to Wallco Ai

scripts/autoid-drunk-batch.js

74 lines

#!/usr/bin/env node
// Drunk-aware Auto-ID batch — NON-DESTRUCTIVE (flags only).
// Calls the drunk-aware /api/admin/cactus/:id/auto-id endpoint for N
// drunk-animals designs. That endpoint runs Gemini vision (No bottle /
// Not animals / Poor edges / Cartoonish) and PERSISTS the dotted-box
// findings to the bad_examples learning corpus. This script just drives it
// across a batch and tallies the breakdown. No publish/delete/deploy.
//
// Usage: node scripts/autoid-drunk-batch.js [--limit 30] [--concurrency 4]
//        [--base http://127.0.0.1:9905] [--include-flagged]
const { execSync } = require('child_process');

const arg = (k, d) => {
  const i = process.argv.indexOf('--' + k);
  if (i === -1) return d;
  const v = process.argv[i + 1];
  return (v && !v.startsWith('--')) ? v : true;
};
const LIMIT = parseInt(arg('limit', '30'), 10);
const CONC = parseInt(arg('concurrency', '4'), 10);
const BASE = String(arg('base', 'http://127.0.0.1:9905'));
const INCLUDE_FLAGGED = !!arg('include-flagged', false);
const DB = process.env.WALLCO_DB || 'dw_unified';

// Prefer designs not yet auto-flagged (find NEW defects); --include-flagged
// re-scans everything. Published drunk-animals with a real file on disk.
const notFlagged = INCLUDE_FLAGGED ? '' : 'AND be.boxes IS NULL ';
const sql =
  "SELECT d.id FROM all_designs d " +
  "LEFT JOIN bad_examples be ON be.design_id=d.id " +
  "WHERE d.category ILIKE '%drunk%' AND d.is_published=TRUE " +
  "AND d.local_path IS NOT NULL " + notFlagged +
  "ORDER BY d.id LIMIT " + LIMIT;

const ids = execSync(`psql -d ${DB} -tAc "${sql}"`, { encoding: 'utf8' })
  .trim().split('\n').map(s => parseInt(s, 10)).filter(Boolean);

console.log(`[autoid-drunk] scanning ${ids.length} drunk-animals via ${BASE} (conc ${CONC})`);
if (!ids.length) { console.log('nothing to scan'); process.exit(0); }

const tally = {};
const flagged = [];
let idx = 0, done = 0;

async function worker() {
  while (idx < ids.length) {
    const id = ids[idx++];
    try {
      const r = await fetch(`${BASE}/api/admin/cactus/${id}/auto-id`);
      if (!r.ok) { tally.err = (tally.err || 0) + 1; done++; continue; }
      const j = await r.json();
      const labels = [...new Set((j.candidates || []).map(b => b.label).filter(Boolean))];
      if (labels.length) {
        flagged.push({ id, labels });
        labels.forEach(l => { tally[l] = (tally[l] || 0) + 1; });
      } else {
        tally.clean = (tally.clean || 0) + 1;
      }
    } catch (e) {
      tally.err = (tally.err || 0) + 1;
    }
    done++;
    if (done % 5 === 0) process.stderr.write(`  ..${done}/${ids.length}\n`);
  }
}

Promise.all(Array.from({ length: CONC }, worker)).then(() => {
  console.log('\n=== Auto-ID breakdown (' + ids.length + ' drunk-animals) ===');
  console.log('labels:', JSON.stringify(tally));
  console.log('flagged ' + flagged.length + ' designs:');
  flagged.forEach(f => console.log('  #' + f.id + '  ' + f.labels.join(', ')));
  console.log('\n(boxes persisted to bad_examples — non-destructive, fates undecided)');
});