Built the missing piece between the test datasheet pipeline and Dataforth's new product API. End-to-end: - Pulled DFWDS (Dataforth Web Datasheet System) VB6 source from AD1\Engineering\ENGR\ATE\Test Datasheets\DFWDS to local for analysis - Decoded its filename validation: A-J prefix decodes (A=10..J=19), all- numeric WO# valid (no leading 0), anything else bad - Ported the validation + move logic to Node (dfwds-process.js) - Built bulk uploader (upload-delta.js) for Hoffman's Swagger API (POST /api/v1/TestReportDataFiles/bulk with OAuth client_credentials) Sanitized 3 prior reference scripts (fetch-server-inventory, test-scenarios, test-upload-two) to read CF_* env vars instead of hardcoded creds. Live drain results: - 897 files moved Test_Datasheets -> For_Web (all valid, no renames, no bad), DFWDS port summary in 1.1s - Pushed entire For_Web (7,061 files) to Hoffman API in 49.7s @ 142/s: Created=803 Updated=114 Unchanged=6,144 Errors=0 - Server count: 489,579 -> 490,382 (+803 net new) Also: - Added clients/dataforth/.gitignore to exclude plaintext Oauth.txt note - Added clients/instrumental-music-center/docs/2026-04-13-ticket-notes.md (ticket write-up of 2026-04-11/12/13 IMC1 RDS removal/SQL migration work) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
137 lines
5.2 KiB
JavaScript
137 lines
5.2 KiB
JavaScript
/**
|
|
* DFWDS-equivalent in Node — moves test datasheets from staging to For_Web,
|
|
* decodes any DOS A-J prefix on filenames, quarantines invalid files.
|
|
*
|
|
* Mirrors the validation logic from the original VB6 DFWDS.bas:
|
|
* - filename must be .txt
|
|
* - must contain a dash
|
|
* - work-order portion (left of dash) must be all-numeric and not start with 0
|
|
* - OR the first char is A-J (DOS-encoded), which decodes to 10-19 and renames
|
|
*
|
|
* Defaults match the original DFWDS_NAMES.txt:
|
|
* --in \\ad2\webshare\Test_Datasheets
|
|
* --bad \\ad2\webshare\Bad_Datasheets
|
|
* --out \\ad2\webshare\For_Web
|
|
* --log \\ad2\webshare\Datasheets_Log
|
|
*
|
|
* Use --dry-run to see what would happen without moving anything.
|
|
*/
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
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 IN_DIR = arg('--in', String.raw`C:\Shares\webshare\Test_Datasheets`);
|
|
const BAD_DIR = arg('--bad', String.raw`C:\Shares\webshare\Bad_Datasheets`);
|
|
const OUT_DIR = arg('--out', String.raw`C:\Shares\webshare\For_Web`);
|
|
const LOG_DIR = arg('--log', String.raw`C:\Shares\webshare\Datasheets_Log`);
|
|
const DRY = flag('--dry-run');
|
|
const LIMIT = parseInt(arg('--limit', '0'), 10);
|
|
|
|
// Per VB funcDecodeWOchar: A-J map to 10..19 (one char -> two digits)
|
|
const PREFIX_DECODE = {
|
|
A: '10', B: '11', C: '12', D: '13', E: '14',
|
|
F: '15', G: '16', H: '17', I: '18', J: '19',
|
|
};
|
|
|
|
function classify(filename) {
|
|
// Returns: {action: 'valid'|'rename'|'bad', newName?: string, reason?: string}
|
|
if (!filename.toLowerCase().endsWith('.txt')) {
|
|
return {action:'bad', reason:'not .txt'};
|
|
}
|
|
const stem = filename.slice(0, -4); // strip .txt
|
|
const dash = stem.indexOf('-');
|
|
if (dash <= 0) return {action:'bad', reason:'no dash or dash at start'};
|
|
|
|
const wo = stem.slice(0, dash).toUpperCase();
|
|
const tail = stem.slice(dash); // includes leading '-'
|
|
|
|
if (/^\d+$/.test(wo)) {
|
|
if (wo.startsWith('0')) return {action:'bad', reason:'WO starts with 0'};
|
|
return {action:'valid'};
|
|
}
|
|
|
|
// Check DOS-encoded: first char A-J, rest all numeric
|
|
const first = wo[0];
|
|
if (PREFIX_DECODE[first] && /^\d+$/.test(wo.slice(1))) {
|
|
const newWo = PREFIX_DECODE[first] + wo.slice(1);
|
|
return {action:'rename', newName: newWo + tail + '.txt'};
|
|
}
|
|
|
|
return {action:'bad', reason:`WO "${wo}" not numeric and not A-J prefixed`};
|
|
}
|
|
|
|
function ensureDir(d) { fs.mkdirSync(d, {recursive: true}); }
|
|
|
|
function moveFile(src, dst) {
|
|
ensureDir(path.dirname(dst));
|
|
if (fs.existsSync(dst)) {
|
|
// overwrite by removing first; rename in same dir is fine but cross-dir on Win sometimes errors if exists
|
|
fs.unlinkSync(dst);
|
|
}
|
|
fs.renameSync(src, dst);
|
|
}
|
|
|
|
function main() {
|
|
if (!fs.existsSync(IN_DIR)) {
|
|
console.error(`[FAIL] input dir not found: ${IN_DIR}`);
|
|
process.exit(1);
|
|
}
|
|
ensureDir(BAD_DIR); ensureDir(OUT_DIR); ensureDir(LOG_DIR);
|
|
|
|
// Log filename matches DFWDS pattern: DFWDS_YYYY_MM_DD.log
|
|
const now = new Date();
|
|
const ymd = `${now.getFullYear()}_${String(now.getMonth()+1).padStart(2,'0')}_${String(now.getDate()).padStart(2,'0')}`;
|
|
const logPath = path.join(LOG_DIR, `DFWDS_${ymd}.log`);
|
|
const logFh = fs.openSync(logPath, 'a');
|
|
function log(msg) {
|
|
const line = `[${new Date().toISOString()}] ${msg}\n`;
|
|
process.stdout.write(line);
|
|
fs.writeSync(logFh, line);
|
|
}
|
|
|
|
log(`=== DFWDS-process start (Node port) ===`);
|
|
log(` in: ${IN_DIR}`);
|
|
log(` out: ${OUT_DIR}`);
|
|
log(` bad: ${BAD_DIR}`);
|
|
log(` dry: ${DRY}, limit: ${LIMIT||'all'}`);
|
|
|
|
const all = fs.readdirSync(IN_DIR).filter(n => fs.statSync(path.join(IN_DIR, n)).isFile());
|
|
const queue = LIMIT ? all.slice(0, LIMIT) : all;
|
|
log(` queued: ${queue.length} files (of ${all.length} in dir)`);
|
|
|
|
const stats = {valid:0, renamed:0, bad:0, errors:0};
|
|
for (const name of queue) {
|
|
const src = path.join(IN_DIR, name);
|
|
const cls = classify(name);
|
|
try {
|
|
if (cls.action === 'valid') {
|
|
const dst = path.join(OUT_DIR, name);
|
|
if (DRY) log(` [DRY VALID] ${name} -> ${dst}`);
|
|
else { moveFile(src, dst); log(` VALID ${name}`); }
|
|
stats.valid++;
|
|
} else if (cls.action === 'rename') {
|
|
const dst = path.join(OUT_DIR, cls.newName);
|
|
if (DRY) log(` [DRY RENAME] ${name} -> ${cls.newName} -> ${dst}`);
|
|
else { moveFile(src, dst); log(` RENAMED ${name} -> ${cls.newName}`); }
|
|
stats.renamed++;
|
|
} else {
|
|
const dst = path.join(BAD_DIR, name);
|
|
if (DRY) log(` [DRY BAD] ${name} (${cls.reason}) -> ${dst}`);
|
|
else { moveFile(src, dst); log(` BAD ${name} (${cls.reason})`); }
|
|
stats.bad++;
|
|
}
|
|
} catch (e) {
|
|
log(` ERR ${name}: ${e.message}`);
|
|
stats.errors++;
|
|
}
|
|
}
|
|
log(`=== summary: valid=${stats.valid} renamed=${stats.renamed} bad=${stats.bad} errors=${stats.errors}`);
|
|
fs.closeSync(logFh);
|
|
console.log(`\n[INFO] log: ${logPath}`);
|
|
}
|
|
|
|
main();
|