Files
claudetools/projects/dataforth-dos/datasheet-pipeline/dfwds-process.js
Mike Swanson dd5c5afd4b Session log + DFWDS Node port + Hoffman API uploader pipeline
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>
2026-04-14 21:06:50 -07:00

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();