Files
claudetools/projects/dataforth-dos/tools/derive-dsca-templates.js
Mike Swanson 3372455b79 dataforth(datasheet): Fix 2 — data-driven DSCA load note (fixes DSCA39 footer artifact)
Root cause of the DSCA39 footer mismatch: the "Standard output load for test is
250 ohms." line is a footer note, not a parameter, but the STAGE 1 extractor
captured it as a (column-truncated) row "Standard output load for te". And the
renderer's OUTSIGTYPE==='CURRENT' emission was wrong on both ends — it printed the
note (after the underline, invisible to the validator gate) for many -C current
models whose staged originals never had it, and never placed it correctly for the
models that do.

Fix is data-driven, matching the rest of the template approach:
- derive-dsca-templates.js: detect the "Standard output load..." line, capture it
  as a per-model `loadNote` property, and exclude it from rows. Regenerated
  dsca-templates.json — surgically clean: only the 5 DSCA39 models changed (lost
  the truncated row, gained loadNote); all 121 others byte-identical.
- datasheet-exact.js: emit `dscaTpl.loadNote` (blank line + note) before the footer
  underline, only for models that have it; removed the OUTSIGTYPE-based emission.

STAGE 3 re-validation: FINAL-TEST CLEAN 85 -> 88, mismatches 9 -> 6, matches
2206 -> 2278. DSCA39-01/02/07 now fully clean; DSCA39-01 byte-content-verified.
No regression — the -C current models stayed clean and no longer carry the
spurious after-underline note.

The 6 remaining dirty models (DSCA38-05/-1793/-19C/-19E, DSCA39-05, DSCA39-1950)
are ALL retest data-vintage: the staged .TXT is an older test run than the DB
latest-wins record (Supply Current / Linearity differ by more than rounding).
Not render bugs — cannot be reconciled against an older sheet.

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

61 lines
3.8 KiB
JavaScript

// Fix 2 STAGE 1 (read-only build): extract per-model DSCA Final-Test templates from staged originals.
// Uses the '===' separator line under the Final-Test header to get exact column spans.
const fs = require('fs'), path = require('path');
const STAGE = 'C:/Shares/test/STAGE';
const OUT = 'C:/Shares/testdatadb/dsca-templates.json';
function walk(d, out) { let it = []; try { it = fs.readdirSync(d, { withFileTypes: true }); } catch { return out; } for (const e of it) { const p = path.join(d, e.name); if (e.isDirectory()) walk(p, out); else if (/\.txt$/i.test(e.name)) out.push(p); } return out; }
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; }
function extract(t) {
const lines = t.replace(/\r\n/g, '\n').split('\n');
const accHdr = lines.find(l => /Error \(%\)/.test(l) && /Status/.test(l)) || '';
const accOut = (accHdr.match(/Output \((?:V|mA)\)|Vout \(V\)/) || ['?'])[0];
let fi = lines.findIndex(l => /FINAL TEST RESULTS/.test(l)); if (fi < 0) return null;
let hi = -1; for (let i = fi + 1; i < lines.length; i++) { if (/Parameter\s+Measured/.test(lines[i])) { hi = i; break; } } if (hi < 0) return null;
const sep = lines[hi + 1] || ''; if (!/=/.test(sep)) return null;
const cols = colSpans(sep); if (cols.length < 4) return null;
const [pc, mc, sc, stc] = cols;
const rows = [];
let loadNote = null;
for (let i = hi + 2; i < lines.length; i++) {
const l = lines[i];
if (/Check List|^\s*_{5,}/.test(l)) break;
if (!l.trim()) continue;
// The "Standard output load for test is ... ohms." line is a footer note, not
// a parameter row — it spans past the name column so column-slicing truncates
// it ("Standard output load for te"). Capture the full line as loadNote and
// keep it out of rows; the renderer emits it (before the footer underline)
// only for models whose staged original actually printed it.
if (/^Standard output load/i.test(l.trim())) { loadNote = l.trim(); continue; }
const name = (l.slice(pc[0], mc[0]) || '').trim();
const spec = (l.slice(sc[0], stc[0]) || '').trim();
if (!name && !spec) continue;
rows.push({ name, spec });
}
return { accOut, rows, loadNote };
}
(async () => {
const files = walk(STAGE, []);
const byModel = {};
for (const f of files) {
let t; try { t = fs.readFileSync(f, 'utf8'); } catch { continue; }
const model = (t.match(/^\s*Model:\s*(\S+)/m) || [])[1] || '';
if (!/^DSCA/i.test(model)) continue;
const tpl = extract(t); if (!tpl) continue;
// keep the sheet with the MOST rows per model (most complete; avoids truncated samples)
if (!byModel[model] || tpl.rows.length > byModel[model].rows.length) byModel[model] = { ...tpl, sheets: (byModel[model] ? byModel[model].sheets : 0) + 1 };
else byModel[model].sheets++;
}
const models = Object.keys(byModel).sort();
console.log('DSCA models templated: ' + models.length);
const out = {}; for (const m of models) { out[m] = { accOut: byModel[m].accOut, rows: byModel[m].rows }; if (byModel[m].loadNote) out[m].loadNote = byModel[m].loadNote; }
fs.writeFileSync(OUT, JSON.stringify(out));
console.log('wrote ' + OUT + ' (' + fs.statSync(OUT).size + ' bytes)');
const rc = {}; for (const m of models) { const n = byModel[m].rows.length; rc[n] = (rc[n] || 0) + 1; }
console.log('row-count distribution (rows:models): ' + Object.entries(rc).sort((a, b) => a[0] - b[0]).map(([n, c]) => n + ':' + c).join(' '));
for (const probe of ['DSCA38-05', 'DSCA34-01', 'DSCA38-08C', 'DSCA30-01']) {
const s = out[probe];
if (s) { console.log('\n' + probe + ' accOut=' + s.accOut + ' rows=' + s.rows.length); s.rows.forEach(r => console.log(' ' + r.name.padEnd(28) + ' | ' + r.spec)); }
else console.log('\n' + probe + ': NOT FOUND');
}
})().catch(e => { console.error('ERR ' + e.message); });