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>
This commit is contained in:
2026-06-18 13:50:23 -07:00
parent 9c04c23ab0
commit abbaaf3c2f
3 changed files with 129 additions and 4 deletions

View File

@@ -522,6 +522,36 @@ function formatAccuracyLine(point, sensorNum, maxIn) {
return ' ' + stimStr + ' ' + calcStr + ' ' + measStr + ' ' + errorStr + ' ' + point.status;
}
/**
* Accuracy row for the Hoffman-mined DSCA33/DSCA45 families, whose original software
* used different column conventions than the voltage/temp models (verified against the
* Hoffman originals):
* - mA-output models store calc (and, for DSCA45, meas) in AMPS -> x1000 to display mA.
* DSCA33 stores meas already in the display unit (NOT scaled); DSCA45 scales both.
* - DSCA33 (AC-RMS) prints stim/calc/meas UNSIGNED (error signed); stim is the AC input
* to 3 decimals.
* - DSCA45 (frequency input) prints stim as an UNSIGNED integer Hz; calc/meas/error SIGNED.
*/
function formatAccuracyLineDSCA3345(point, model, accOut) {
const scale = /mA/.test(accOut || '') ? 1000 : 1;
const isDSCA45 = /^DSCA45/i.test((model || '').trim());
// values were computed in QB single precision; recover the single before formatting
// so last-digit rounding at the .5 boundary matches the original (Math.fround).
const num = (val, decimals, signed) => ((signed && val >= 0) ? '+' : '') + Math.fround(val).toFixed(decimals);
let stimStr, calcStr, measStr;
if (isDSCA45) {
stimStr = num(point.stim, 0, false).padStart(8);
calcStr = num(point.calc * scale, 3, true).padStart(7);
measStr = num(point.meas * scale, 3, true).padStart(7);
} else {
stimStr = num(point.stim, 3, false).padStart(8);
calcStr = num(point.calc * scale, 3, false).padStart(7);
measStr = num(point.meas, 3, false).padStart(7);
}
const errorStr = num(point.error, 3, true).padStart(8);
return ' ' + stimStr + ' ' + calcStr + ' ' + measStr + ' ' + errorStr + ' ' + point.status;
}
/**
* Set text at a specific column position (0-indexed) in a string.
* Pads with spaces if the string is shorter than the target column.
@@ -665,6 +695,10 @@ function generateExactDatasheet(record, specs) {
}
for (const point of parsed.accuracy) {
if (dscaTpl && Array.isArray(dscaTpl.accHeader)) {
lines.push(formatAccuracyLineDSCA3345(point, record.model_number, dscaTpl.accOut));
continue;
}
lines.push(formatAccuracyLine(point, sensorNum, maxIn));
}
lines.push('');
@@ -709,6 +743,7 @@ function generateExactDatasheet(record, specs) {
h2 = setCol(h2, 69, '='.repeat(6));
lines.push(h2);
const is3345 = Array.isArray(dscaTpl.accHeader); // Hoffman-mined DSCA33/45
let mi = 0, si = 0;
for (const row of dscaTpl.rows) {
const spec = (row.spec || '').trim();
@@ -719,8 +754,12 @@ function generateExactDatasheet(record, specs) {
? formatMeasuredExact(parsed.statusEntries[dscaTpl.slotMap[si++]])
: measurements[mi++];
if (m) {
// measured value right-justified ending at col 38, unit at col 40
const v = String(m.valStr);
// measured value right-justified ending at col 38, unit at col 40.
// DSCA33/45 follow QB's fixed 6-char number field: a value that would
// overflow drops its leading zero to fit ("-0.0005" (7) -> "-.0005" (6));
// values that already fit (e.g. "-0.750", "0.0000") keep it.
let v = String(m.valStr);
if (is3345 && v.length > 6) v = v.replace(/^-0\./, '-.');
line = setCol(line, 39 - v.length, v);
if (su.unit) line = setCol(line, 40, su.unit);
}
@@ -728,8 +767,12 @@ function generateExactDatasheet(record, specs) {
line = setCol(line, 59 - su.valuePart.length, su.valuePart);
if (su.unit) line = setCol(line, 60, su.unit);
line = setCol(line, 70, m ? m.passFail : 'PASS');
} else if (is3345 && !/withstand|hi-?pot/i.test(row.name)) {
// DSCA33/45 spec-less rows that are NOT a pass/fail test (e.g. the
// "Zero-Crossing Input" / "TTL Input" section sub-heads) carry no status.
// (Leave the row as just the name.)
} else {
// no spec => 240VAC Withstand / Hi-Pot style row: blank measured + PASS
// spec-less pass/fail row (240VAC Withstand / Hi-Pot): blank measured + PASS
line = setCol(line, 70, 'PASS');
}
lines.push(line);
@@ -830,6 +873,9 @@ function generateExactDatasheet(record, specs) {
lines.push(setCol(TAB5 + 'Pins Straight: _____', 44, 'Module Header: _____'));
lines.push('');
lines.push(setCol(TAB5 + 'Tested by: _____________', 44, 'QC: _______________'));
} else if (/^DSCA33/i.test((record.model_number || '').trim())) {
// DSCA33 originals print just the centered "Check List" header (no items).
lines.push(' Check List');
} else if (family !== 'DSCA') {
lines.push(' Check List');
lines.push('');