/** * One-time back-population of api_uploaded_at from server_inventory.txt. * * Reads SN list, UPDATEs test_records.api_uploaded_at = NOW() in batches * for records whose serial_number appears in the inventory. * * Usage: node back-populate-api-uploaded.js [--inventory path] [--batch 1000] [--dry-run] */ const fs = require('fs'); const db = require('./db'); const args = process.argv.slice(2); const arg = (n, d) => { const i = args.indexOf(n); return i >= 0 ? args[i+1] : d; }; const flag = n => args.includes(n); const INVENTORY = arg('--inventory', 'C:\\ProgramData\\dataforth-uploader\\server_inventory.txt'); const BATCH = parseInt(arg('--batch', '1000'), 10); const DRY = flag('--dry-run'); async function main() { if (!fs.existsSync(INVENTORY)) { console.error(`[FAIL] inventory not found: ${INVENTORY}`); process.exit(1); } const data = fs.readFileSync(INVENTORY, 'utf8'); const sns = data.split(/\r?\n/).map(s => s.trim()).filter(Boolean); console.log(`[INFO] inventory: ${sns.length} serial numbers`); console.log(`[INFO] batch size: ${BATCH} dry-run: ${DRY}`); const t0 = Date.now(); let totalMatched = 0; for (let i = 0; i < sns.length; i += BATCH) { const chunk = sns.slice(i, i + BATCH); const placeholders = chunk.map((_, j) => `$${j + 1}`).join(','); if (DRY) { const row = await db.queryOne( `SELECT COUNT(*) as c FROM test_records WHERE serial_number IN (${placeholders}) AND api_uploaded_at IS NULL`, chunk, ); totalMatched += parseInt(row.c, 10) || 0; } else { const result = await db.execute( `UPDATE test_records SET api_uploaded_at = NOW() WHERE serial_number IN (${placeholders}) AND api_uploaded_at IS NULL`, chunk, ); totalMatched += result.rowCount || 0; } if ((i / BATCH) % 20 === 0) { const rate = (i + chunk.length) / Math.max(1, (Date.now() - t0) / 1000); const eta = Math.round((sns.length - i - chunk.length) / Math.max(1, rate)); console.log(` progress ${i + chunk.length}/${sns.length} matched-so-far=${totalMatched} rate=${rate.toFixed(0)}/s eta=${eta}s`); } } const elapsed = ((Date.now() - t0) / 1000).toFixed(1); console.log(`\n[DONE] ${elapsed}s`); console.log(` inventory size: ${sns.length}`); console.log(` ${DRY ? 'would update' : 'updated'}: ${totalMatched}`); // Sanity: how many records have api_uploaded_at set vs null? const tot = await db.queryOne(`SELECT COUNT(*) as c FROM test_records`); const set = await db.queryOne(`SELECT COUNT(*) as c FROM test_records WHERE api_uploaded_at IS NOT NULL`); const nul = await db.queryOne(`SELECT COUNT(*) as c FROM test_records WHERE api_uploaded_at IS NULL`); console.log(`\n[DB STATE]`); console.log(` total records: ${tot.c}`); console.log(` api_uploaded_at SET: ${set.c}`); console.log(` api_uploaded_at NULL: ${nul.c}`); await db.close(); } main().catch(e => { console.error('[FATAL]', e); process.exit(1); });