Files
claudetools/projects/dataforth-dos/tools/slotmap-from-hoffman.js
Mike Swanson abbaaf3c2f dataforth(datasheet): DSCA33/45 accuracy-data reverse-engineering — 54/56 validated
Cracked the DSCA33/45 accuracy-block numeric formatting against the Hoffman originals
(formatAccuracyLineDSCA3345):
- mA-output models store calc (and, for DSCA45, meas) in AMPS -> x1000 to display mA;
  DSCA33 stores meas already in display unit (NOT scaled), DSCA45 scales both.
- DSCA33 (AC-RMS): stim/calc/meas UNSIGNED, error signed; stim is AC input to 3 dp.
- DSCA45 (frequency): stim is an UNSIGNED integer Hz; calc/meas/error SIGNED.
- Math.fround on accuracy values (QB single-precision rounding), matching the Final-Test fix.
Final-Test fixes too: leading-zero drop only when the value overflows QB's 6-char field
("-0.0005"->"-.0005", but "-0.750" keeps its zero); spec-less section sub-heads
(Zero-Crossing Input / TTL Input) render with NO status (only Withstand/Hi-Pot get PASS);
DSCA33 prints a "Check List" header after the underline.

slotmap-from-hoffman.js (new): derive slotMaps for the models the staged multi-unit
derivation couldn't (vintage-heavy) by matching the Hoffman _srcSerial original's
Final-Test measured values (at display precision) to the DB STATUS entries. Recovered
all 13 remaining DSCA33 models.

Validation (validate-dsca3345.js, content-normalized byte-compare vs live Hoffman
originals): 54 of 56 models PASS and are marked validated:true (the render gate).
2 holdouts (DSCA33-04A, DSCA33-1891) each have ONE accuracy cert at a rounding boundary
where fround rounds opposite to the original; left UNvalidated -> still render null
(safe). DSCA33-1948 + DSCA45-1746 (24 units) have no Hoffman original.

Gate now OPEN for the 54 validated models (render live); 2 holdouts + the no-template
pair stay null. Publishing the api_uploaded_at IS NULL gap next (never re-pushes the
~7,157 pristine originals).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 13:50:23 -07:00

80 lines
5.5 KiB
JavaScript

// Derive slotMaps for DSCA33/45 models that still render null (no slotMap), using the
// Hoffman _srcSerial original as the oracle: match its Final-Test measured values, in
// order, to the DB record's value-bearing STATUS entries (numeric greedy). Writes the
// slotMap into dsca33-45-templates.json. --apply to persist.
process.env.DSCA_VALIDATE_MODE = '1';
const fs = require('fs'), https = require('https');
const db = require('./database/db');
const dse = require('./templates/datasheet-exact');
const TPL = './dsca33-45-templates.json';
const c = JSON.parse(fs.readFileSync('C:\\ProgramData\\dataforth-uploader\\credentials.json', 'utf8'));
const APPLY = process.argv.includes('--apply');
const only = process.argv.slice(2).filter(a => !a.startsWith('--'));
function req(m, uri, h, b) { return new Promise((res, rej) => { const u = new URL(uri); const r = https.request({ hostname: u.hostname, port: 443, path: u.pathname, method: m, headers: h, timeout: 30000 }, x => { let d = ''; x.on('data', c => d += c); x.on('end', () => { try { res(JSON.parse(d)); } catch { res({ _raw: d }); } }); }); r.on('error', rej); if (b) r.write(b); r.end(); }); }
function colSpans(sep) { const cols = []; let m; const re = /=+/g; while ((m = re.exec(sep))) cols.push([m.index, m.index + m[0].length]); return cols; }
// Final-Test measured numeric values from a Hoffman original, in row order (spec rows only).
function hoffmanMeasured(content) {
const L = content.replace(/\r/g, '').split('\n');
const fi = L.findIndex(l => /FINAL TEST RESULTS/.test(l)); if (fi < 0) return null;
let hi = -1; for (let i = fi + 1; i < L.length; i++) if (/Parameter\s+Measured/.test(L[i])) { hi = i; break; } if (hi < 0) return null;
const cols = colSpans(L[hi + 1]); if (cols.length < 4) return null;
const [pc, mc, sc] = cols;
const out = [];
for (let i = hi + 2; i < L.length; i++) {
const l = L[i]; if (/^\s*_{5,}|Check List|It is hereby/.test(l)) break; if (!l.trim()) continue;
if (/^\s*Standard output load/i.test(l)) continue;
const meas = (l.slice(mc[0], sc[0]) || '').trim().split(/\s+/)[0];
const spec = (l.slice(sc[0], cols[3][0]) || '').trim();
if (!spec) continue; // spec-less row (Withstand/Hi-Pot/section head)
const v = parseFloat(meas);
if (isNaN(v)) return null;
out.push(v);
}
return out;
}
(async () => {
const tpl = JSON.parse(fs.readFileSync(TPL, 'utf8'));
const form = Object.entries({ grant_type: 'client_credentials', client_id: c.CF_CLIENT_ID, client_secret: c.CF_CLIENT_SECRET, scope: c.CF_SCOPE }).map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v)).join('&');
const t = (await req('POST', c.CF_TOKEN_URL, { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(form) }, form)).access_token;
const models = (only.length ? only : Object.keys(tpl)).filter(m => tpl[m] && !Array.isArray(tpl[m].slotMap));
let done = 0; const failed = [];
for (const m of models) {
const sn = tpl[m]._srcSerial; if (!sn) { failed.push(m + '(no srcSerial)'); continue; }
const rec = await db.queryOne('SELECT raw_data FROM test_records WHERE serial_number=$1 AND model_number=$2', [sn, m]);
if (!rec) { failed.push(m + '(no DB rec)'); continue; }
const g = await req('GET', c.CF_API_BASE + '/api/v1/TestReportDataFiles/' + encodeURIComponent(sn), { Authorization: 'Bearer ' + t });
if (!g.Content) { failed.push(m + '(no Hoffman)'); continue; }
const measured = hoffmanMeasured(g.Content);
const p = dse.parseRawData(rec.raw_data, 'DSCA');
if (!measured || !p) { failed.push(m + '(parse)'); continue; }
const specRows = tpl[m].rows.filter(r => (r.spec || '').trim()).length;
if (measured.length !== specRows) { failed.push(m + '(rows ' + measured.length + '!=' + specRows + ')'); continue; }
// numeric greedy match: each Final-Test measured value -> next status entry with equal value
const map = []; let j = 0; let ok = true;
for (const mv of measured) {
let found = -1;
for (let k = j; k < p.statusEntries.length; k++) {
const s = p.statusEntries[k];
if (!s || s.length <= 4) continue;
// compare at DISPLAY precision: fround + toFixed(decimal-code), like the
// renderer — so near-zero (raw 4.4e-5 displayed "0.0000") matches the
// Hoffman displayed value instead of failing a raw-value tolerance.
const code = parseInt(s[s.length - 1], 10);
const val = parseFloat(s.substring(4, s.length - 1).trim());
if (isNaN(val)) continue;
const disp = parseFloat(Math.fround(val).toFixed(isNaN(code) ? 1 : code));
if (Math.abs(disp - mv) < 1e-9) { found = k; break; }
}
if (found < 0) { ok = false; break; }
map.push(found); j = found + 1;
}
if (ok && map.length === specRows) { tpl[m].slotMap = map; done++; console.log(' ' + m.padEnd(13) + ' slotMap=[' + map.join(',') + '] (oracle ' + sn + ')'); }
else failed.push(m + '(no clean match)');
}
console.log('\nderived: ' + done);
if (failed.length) console.log('failed: ' + failed.join(', '));
if (APPLY) { fs.writeFileSync(TPL, JSON.stringify(tpl)); console.log('[APPLY] wrote ' + TPL); }
else console.log('(dry run — pass --apply to write)');
await db.close();
})().catch(e => { console.error('ERR', e.message, e.stack); process.exit(1); });