dataforth(datasheet): Fix 2 STAGE 2 — wire DSCA per-model templates into render
Deployed file is C:\Shares\testdatadb\templates\datasheet-exact.js; this
reconciles the repo copy + adds dsca-templates.json (STAGE 1 output).
What changed in generateExactDatasheet (DSCA family only; 5B/8B/7B/DSCT/SCMVAS
paths byte-unchanged):
- Load dsca-templates.json once at module top (126 per-model layouts).
- DSCA Final-Test now renders names + specs from the staged template rows, not
the single hardcoded DATA_LINES['DSCA'] + buildTSpecs DSCA branch.
- Value-bearing raw_data STATUS groups map positionally onto the spec-bearing
template rows; empty-spec rows (240VAC Withstand / Hi-Pot) render blank+PASS.
Removed the duplicate hardcoded 240VAC/Hi-Pot footer for DSCA (now rows).
- ACCURACY header uses the template accOut ("Output (V)"/"Output (mA)") with '-'
rule separators instead of "Vout (V)" + '='.
- Header/columns match the staged originals (Measured Value*, 25/15/19/6 rule).
Two real bugs fixed (both are the handoff's "lines drop" / wrong-value defect):
- formatMeasuredExact reads the value from index 4 so negative signs survive
("PASS-4.24..." -> "-4", not "4"); also decimal-code N -> toFixed(N) exactly
(DSCA differs from 5B/8B where code 2 means 1 decimal).
- parseRawData no longer consumes the first DSCA STATUS group as a bare
step-response line when that line is absent (dropped 3 rows on e.g. DSCA39-01).
Safety: when value count != spec-row count the positional zip is ambiguous
(subtype measures load points the template omits, e.g. DSCA49 5mA pair), so the
cert is SKIPPED (null) and left for STAGE 3 per-subtype mapping rather than
emitting misaligned data.
Validation: DSCA38-05 (SN 180224-1) Final-Test block byte-identical to its
staged original. 92/126 templated models render cleanly; 7 ambiguous + 27
no-spec skip. Remaining ACCURACY-block spacing diffs are the deferred cosmetic
gap. NOT YET LIVE: testdatadb service not restarted, nothing re-pushed to
Hoffman (STAGE 3 gate).
Coverage gap to resolve before publish: only 126/357 DSCA models in the DB have
a staged template (56,074 certs, 70.1%); 231 models / 23,866 certs have none and
now render null — needs a STAGE 1 extension (more staged originals).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -7,6 +7,17 @@
|
||||
|
||||
const { getFamily } = require('../parsers/spec-reader');
|
||||
|
||||
// DSCA per-model Final-Test templates (Fix 2 STAGE 1 output). Each entry is
|
||||
// { accOut: 'Output (V)'|'Output (mA)', rows: [{name, spec}, ...] }, extracted
|
||||
// byte-accurately from the staged originals. This is the AUTHORITATIVE source
|
||||
// of DSCA parameter names + specs + accuracy output label; loaded once.
|
||||
let DSCA_TEMPLATES = {};
|
||||
try {
|
||||
DSCA_TEMPLATES = require('../dsca-templates.json');
|
||||
} catch (e) {
|
||||
DSCA_TEMPLATES = {};
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// DATA LINES: parameter names and units per family
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -189,12 +200,18 @@ function parseRawData(rawData, family) {
|
||||
}
|
||||
}
|
||||
|
||||
// Next line: step response / placeholders
|
||||
// Next line: step response / placeholders.
|
||||
// SCM5B/8B: "0","0",value DSCT: just value. Many DSCA models OMIT this bare
|
||||
// line and go straight to the STATUS groups; consuming a STATUS group here
|
||||
// drops a Final-Test row (the "lines drop" defect). For DSCA, skip consuming
|
||||
// when the line is actually a STATUS group (starts with PASS/FAIL).
|
||||
if (lineIdx < lines.length) {
|
||||
const parts = parseCSVLine(lines[lineIdx++]);
|
||||
// SCM5B/8B: "0","0",value DSCT: just value
|
||||
const lastVal = parts[parts.length - 1];
|
||||
result.stepResponse = parseFloat(lastVal) || 0;
|
||||
const looksLikeStatus = /^"?(PASS|FAIL)/i.test(lines[lineIdx].trim());
|
||||
if (!(family === 'DSCA' && looksLikeStatus)) {
|
||||
const parts = parseCSVLine(lines[lineIdx++]);
|
||||
const lastVal = parts[parts.length - 1];
|
||||
result.stepResponse = parseFloat(lastVal) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Remaining lines: STATUS groups
|
||||
@@ -297,6 +314,39 @@ function formatMeasured(statusStr) {
|
||||
return { passFail, formatted, value };
|
||||
}
|
||||
|
||||
/**
|
||||
* DSCA measured-value formatter. The DSCA Final-Test STATUS$ entries use the
|
||||
* trailing digit as a literal decimal-place count (code N -> toFixed(N)),
|
||||
* UNLIKE the 5B/8B QB format strings where code 2 means 1 decimal. Returns the
|
||||
* trimmed value string (caller column-aligns it) plus the PASS/FAIL prefix.
|
||||
*/
|
||||
function formatMeasuredExact(statusStr) {
|
||||
if (!statusStr || statusStr.length <= 4) return null;
|
||||
const passFail = statusStr.substring(0, 4);
|
||||
const decimalDigit = statusStr[statusStr.length - 1];
|
||||
// char at index 4 is either a space (positive) or '-' (negative); start there
|
||||
// so negative signs survive (e.g. "PASS-4.2424060" -> "-4", not "4").
|
||||
const valueStr = statusStr.substring(4, statusStr.length - 1).trim();
|
||||
const value = parseFloat(valueStr);
|
||||
if (isNaN(value)) return { passFail, valStr: valueStr, value: NaN };
|
||||
const d = parseInt(decimalDigit, 10);
|
||||
const valStr = isNaN(d) ? value.toFixed(1) : value.toFixed(d);
|
||||
return { passFail, valStr, value };
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a DSCA template spec string into its value part and trailing unit.
|
||||
* e.g. "< 30 mA" -> { valuePart: "< 30", unit: "mA" } (internal spacing kept,
|
||||
* so the value part can be right-aligned to match the staged column layout).
|
||||
* "+/- 11 ppm/mA" -> { valuePart: "+/- 11", unit: "ppm/mA" }.
|
||||
*/
|
||||
function splitSpecUnit(spec) {
|
||||
const s = String(spec);
|
||||
const m = s.match(/^(.*\S)\s+(\S+)$/);
|
||||
if (m) return { valuePart: m[1], unit: m[2] };
|
||||
return { valuePart: s.trim(), unit: '' };
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Format TSPEC display string from spec values
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -439,9 +489,7 @@ function buildTSpecs(specs, family, stepResponse) {
|
||||
function formatAccuracyLine(point, sensorNum, maxIn) {
|
||||
let stimStr;
|
||||
if ((sensorNum >= 3 && sensorNum <= 6) || sensorNum === 7) {
|
||||
// Temperature: +####.## (thermocouples 3-6 AND RTD 7 — Dataforth RTD
|
||||
// datasheets report the input as temperature, not the raw resistance.
|
||||
// The .DAT/raw_data stimulus is already in degrees C, so no conversion.)
|
||||
// Temperature: +####.##
|
||||
stimStr = formatSigned(point.stim, 2, 8);
|
||||
} else {
|
||||
// Voltage/Current: +###.###
|
||||
@@ -498,6 +546,14 @@ function generateExactDatasheet(record, specs) {
|
||||
return generateSCMVASDatasheet(record);
|
||||
}
|
||||
|
||||
// DSCA STAGE 2: the per-model staged template is the authoritative Final-Test
|
||||
// layout (names + specs + accuracy output label). No template means no staged
|
||||
// original was available -> do not guess the layout; skip this cert.
|
||||
const dscaTpl = (family === 'DSCA')
|
||||
? (DSCA_TEMPLATES[(record.model_number || '').trim()] || null)
|
||||
: null;
|
||||
if (family === 'DSCA' && !dscaTpl) return null;
|
||||
|
||||
const parsed = (family === 'SCM7B')
|
||||
? parse7BRawData(record.raw_data)
|
||||
: parseRawData(record.raw_data, family);
|
||||
@@ -557,15 +613,18 @@ function generateExactDatasheet(record, specs) {
|
||||
// Input column header based on sensor type
|
||||
let inputHeader;
|
||||
if ((sensorNum >= 3 && sensorNum <= 6) || sensorNum === 7) {
|
||||
// RTD (7) reports temperature, same as thermocouples (3-6).
|
||||
inputHeader = ' Temp. (C)';
|
||||
} else if (sensorNum === 2 || sensorNum === 9) {
|
||||
inputHeader = ' Iin (mA)';
|
||||
} else {
|
||||
inputHeader = (maxIn != null && maxIn < 1) ? ' Vin (mV)' : ' Vin (V)';
|
||||
}
|
||||
lines.push(' ' + inputHeader + ' Vout (V) Vout (V)* Error (%) Status');
|
||||
lines.push(TAB5 + '========== ========== ========== ========= ========');
|
||||
// DSCA labels its accuracy output column "Output (V)"/"Output (mA)" (from the
|
||||
// template) with '-' rule separators; 5B/8B/etc. use "Vout (V)" with '='.
|
||||
const accOut = (family === 'DSCA' && dscaTpl) ? dscaTpl.accOut : 'Vout (V)';
|
||||
const accSep = (family === 'DSCA') ? '-' : '=';
|
||||
lines.push(' ' + inputHeader + ' ' + accOut + ' ' + accOut + '* Error (%) Status');
|
||||
lines.push(TAB5 + accSep.repeat(10) + ' ' + accSep.repeat(10) + ' ' + accSep.repeat(10) + ' ' + accSep.repeat(9) + ' ' + accSep.repeat(8));
|
||||
|
||||
for (const point of parsed.accuracy) {
|
||||
lines.push(formatAccuracyLine(point, sensorNum, maxIn));
|
||||
@@ -577,6 +636,62 @@ function generateExactDatasheet(record, specs) {
|
||||
// QB column positions (1-indexed): TAB(31), TAB(47), TAB(60-speclen), TAB(61), TAB(71)
|
||||
lines.push(' FINAL TEST RESULTS');
|
||||
lines.push('');
|
||||
if (family === 'DSCA') {
|
||||
// DSCA Final-Test renders from the per-model staged template: the rows give
|
||||
// the parameter names + specs (and accuracy label) directly; the value-bearing
|
||||
// raw_data STATUS groups map positionally onto the spec-bearing rows. Rows with
|
||||
// an empty spec (240VAC Withstand / Hi-Pot) carry no measured value and render
|
||||
// as PASS. Header/column scheme matches the staged originals.
|
||||
|
||||
// Value-bearing measurements in source order (drop "PASS"/"" padding entries).
|
||||
const measurements = [];
|
||||
for (const s of parsed.statusEntries) {
|
||||
const m = formatMeasuredExact(s);
|
||||
if (m) measurements.push(m);
|
||||
}
|
||||
const specRowCount = dscaTpl.rows.filter(r => (r.spec || '').trim()).length;
|
||||
// The simple positional zip is only sound when there is exactly one measured
|
||||
// value per spec-bearing row. When counts differ, this subtype uses a slot
|
||||
// layout the zip can't resolve (e.g. raw_data records load points the template
|
||||
// omits), so emitting would misalign values onto wrong rows. Skip and flag for
|
||||
// STAGE 3 per-subtype mapping rather than publish wrong data ("do not guess").
|
||||
if (measurements.length !== specRowCount) return null;
|
||||
|
||||
let h1 = setCol('', 12, 'Parameter');
|
||||
h1 = setCol(h1, 31, 'Measured Value*');
|
||||
h1 = setCol(h1, 51, 'Specification');
|
||||
h1 = setCol(h1, 69, 'Status');
|
||||
lines.push(h1);
|
||||
let h2 = setCol('', 4, '='.repeat(25));
|
||||
h2 = setCol(h2, 31, '='.repeat(15));
|
||||
h2 = setCol(h2, 48, '='.repeat(19));
|
||||
h2 = setCol(h2, 69, '='.repeat(6));
|
||||
lines.push(h2);
|
||||
|
||||
let mi = 0;
|
||||
for (const row of dscaTpl.rows) {
|
||||
const spec = (row.spec || '').trim();
|
||||
let line = setCol('', 4, row.name);
|
||||
if (spec) {
|
||||
const su = splitSpecUnit(spec);
|
||||
const m = measurements[mi++];
|
||||
if (m) {
|
||||
// measured value right-justified ending at col 38, unit at col 40
|
||||
const v = String(m.valStr);
|
||||
line = setCol(line, 39 - v.length, v);
|
||||
if (su.unit) line = setCol(line, 40, su.unit);
|
||||
}
|
||||
// spec value-part right-justified ending at col 58, unit at col 60
|
||||
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 {
|
||||
// no spec => 240VAC Withstand / Hi-Pot style row: blank measured + PASS
|
||||
line = setCol(line, 70, 'PASS');
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
} else {
|
||||
// QB: TAB(12); "Parameter"; TAB(30); "Measured Value"; TAB(51); "Specification "; TAB(70); "Status"
|
||||
let hdr1 = setCol('', 11, 'Parameter');
|
||||
hdr1 = setCol(hdr1, 29, 'Measured Value');
|
||||
@@ -626,6 +741,7 @@ function generateExactDatasheet(record, specs) {
|
||||
|
||||
lines.push(line);
|
||||
}
|
||||
} // end non-DSCA Final Test Results
|
||||
|
||||
// ---- Footer ----
|
||||
// 240 VAC / Hi-Pot (conditional by family/model)
|
||||
@@ -649,9 +765,6 @@ function generateExactDatasheet(record, specs) {
|
||||
let hp = setCol(TAB5 + 'Hi-Pot', 70, 'PASS');
|
||||
lines.push(hp);
|
||||
}
|
||||
} else if (family === 'DSCA') {
|
||||
lines.push(TAB5 + '240VAC Withstand' + ''.padEnd(50) + 'PASS');
|
||||
lines.push(TAB5 + 'Hi-Pot' + ''.padEnd(60) + 'PASS');
|
||||
} else if (family === 'DSCT') {
|
||||
lines.push(TAB5 + '240 VAC Withstand' + ''.padEnd(49) + 'PASS');
|
||||
lines.push(TAB5 + 'Hi-Pot' + ''.padEnd(60) + 'PASS');
|
||||
|
||||
Reference in New Issue
Block a user