← back to Dw Marketing Reels
scripts/jewelry-sweep-pending.mjs
71 lines
#!/usr/bin/env node
/**
* jewelry-sweep-pending.mjs — sweep the STAGED reel queue for jewelry-counter
* content so nothing already-queued slips through when posting is re-armed.
* TK-12012 / Steve 2026-09-22 (jewelry ONLY).
*
* For every reel in data/reels.json that is NOT already posted and whose mp4
* exists locally, run the same frame-classify jewelry gate publish-social.mjs
* uses. Any reel with a jewelry-counter frame is QUARANTINED in place —
* publish.instagram/tiktok stamped {status:'held-jewelry-review'} — so it will
* never auto-post. Room-setting reels are untouched (scope = jewelry only).
*
* Reversible: writes data/reels.json.jewelry-sweep-bak-<ts> before any change,
* and only writes the manifest if something was actually quarantined.
* node jewelry-sweep-pending.mjs # sweep + quarantine
* node jewelry-sweep-pending.mjs --dry # report only, no writes
*/
import fs from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { gateReel } from './jewelry-gate.mjs';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const MAN = join(ROOT, 'data', 'reels.json');
const DRY = process.argv.includes('--dry');
const TERMINAL = new Set(['posted', 'already-posted', 'posted-unverified']);
const now = () => new Date().toISOString();
const reels = JSON.parse(fs.readFileSync(MAN, 'utf8'));
let scanned = 0, quarantined = 0, clean = 0, undecided = 0, skipped = 0;
const changes = [];
for (const reel of reels) {
const st = reel.publish?.instagram?.status;
if (TERMINAL.has(st)) { skipped++; continue; }
if (st === 'held-jewelry-review') { quarantined++; continue; } // already held
const local = join(ROOT, 'reels', reel.file);
if (!fs.existsSync(local)) { skipped++; continue; }
scanned++;
const gate = await gateReel(local);
if (gate.reason === 'jewelry') {
quarantined++;
const held = { status: 'held-jewelry-review', reason: 'jewelry',
note: `swept: jewelry-counter frame (${gate.jewelryFrames}/${gate.sampled} sampled) — quarantined`, at: now() };
reel.publish = reel.publish || {};
reel.publish.instagram = { ...held };
reel.publish.tiktok = { ...held };
changes.push({ file: reel.file, jewelryFrames: gate.jewelryFrames, sampled: gate.sampled });
console.log(` QUARANTINE ${reel.file} (${gate.jewelryFrames}/${gate.sampled} jewelry frames)`);
} else if (gate.reason === 'clean') {
clean++;
console.log(` clean ${reel.file}`);
} else {
undecided++;
console.log(` undecided ${reel.file} (${gate.note || gate.reason}) — will be held at publish (fail-closed)`);
}
}
console.log(`\nswept ${scanned} staged reel(s): ${changes.length} newly quarantined, ${clean} clean, ${undecided} undecided(held-at-publish); ${quarantined} total held; ${skipped} skipped(posted/missing).`);
if (changes.length && !DRY) {
const bak = `${MAN}.jewelry-sweep-bak-${Date.now()}`;
fs.copyFileSync(MAN, bak);
fs.writeFileSync(MAN, JSON.stringify(reels, null, 2));
console.log(`wrote quarantine marks to reels.json (backup: ${bak})`);
} else if (changes.length) {
console.log('--dry: no writes.');
} else {
console.log('no jewelry reels found in the staged queue — nothing to quarantine.');
}