Compare commits
2 Commits
6260b5a488
...
2fadd98e81
| Author | SHA1 | Date | |
|---|---|---|---|
| 2fadd98e81 | |||
| 1c630552e0 |
@@ -17,6 +17,8 @@ Categories (the `[type]` tag): _(none)_ = skill/command execution failure ·
|
||||
|
||||
<!-- Append entries below this line -->
|
||||
|
||||
2026-06-18 | AD2 | dataforth/hoffman-publish | 9 of 2440 DSCA/8B gap pushes errored during 8B/5B/SCM Created-publish [ctx: created=2348 errors=9]
|
||||
|
||||
2026-06-18 | GURU-5070 | agy/search | gemini CLI threw ineligible/projectId setup error (throwIneligibleOrProjectIdError), empty response after 3 attempts [ctx: mode=search host=GURU-5070]
|
||||
|
||||
2026-06-18 | GURU-5070 | agy | gemini returned no response (empty after 3 attempts) [ctx: mode=search err= at process.processTicksAndRejections (node:internal/process/task_queues:104:]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -366,15 +366,15 @@ function formatMeasuredExact(statusStr) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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" }.
|
||||
* Split a spec string into its value part and unit. The unit is everything after the
|
||||
* last number, so multi-word units survive ("< 17 uA RMS" -> unit "uA RMS", not "RMS";
|
||||
* "< 30 mA" -> "mA"; "+/- 11 ppm/mA" -> "ppm/mA"; "44+/- 7 dB" -> "dB"). Internal
|
||||
* spacing in the value part is kept for right-alignment.
|
||||
*/
|
||||
function splitSpecUnit(spec) {
|
||||
const s = String(spec);
|
||||
const m = s.match(/^(.*\S)\s+(\S+)$/);
|
||||
if (m) return { valuePart: m[1], unit: m[2] };
|
||||
const m = s.match(/^(.*\d[\d.]*)\s+(\S.*?)\s*$/);
|
||||
if (m) return { valuePart: m[1].replace(/\s+$/, ''), unit: m[2] };
|
||||
return { valuePart: s.trim(), unit: '' };
|
||||
}
|
||||
|
||||
@@ -545,21 +545,34 @@ function formatAccuracyLine(point, sensorNum, maxIn) {
|
||||
* to 3 decimals.
|
||||
* - DSCA45 (frequency input) prints stim as an UNSIGNED integer Hz; calc/meas/error SIGNED.
|
||||
*/
|
||||
function formatAccuracyLineDSCA3345(point, inputType, accOut) {
|
||||
const scale = /mA/.test(accOut || '') ? 1000 : 1;
|
||||
const isDSCA45 = inputType === 'freq'; // frequency-input (Hz stim); else AC-RMS
|
||||
// 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).
|
||||
function formatAccuracyLineDSCA3345(point, inputType, outUnit, inUnit) {
|
||||
// Output (calc/meas) is stored in base units; x1000 to display mA/mV. QB single
|
||||
// precision recovered with Math.fround so .5-boundary rounding matches the original.
|
||||
// Units come from the accHeader (the spec files are missing for these), so this works
|
||||
// without specs. Sign + stim conventions differ by input type:
|
||||
// freq - stim = unsigned integer Hz; calc/meas signed, scaled.
|
||||
// ac - stim/calc/meas unsigned; AC-RMS meas already in display unit (NOT scaled).
|
||||
// std - voltage/current/temperature input, all signed; calc/meas scaled.
|
||||
const oscale = /m[AV]/.test(outUnit || '') ? 1000 : 1;
|
||||
const num = (val, decimals, signed) => ((signed && val >= 0) ? '+' : '') + Math.fround(val).toFixed(decimals);
|
||||
let stimStr, calcStr, measStr;
|
||||
if (isDSCA45) {
|
||||
if (inputType === 'freq') {
|
||||
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 {
|
||||
calcStr = num(point.calc * oscale, 3, true).padStart(7);
|
||||
measStr = num(point.meas * oscale, 3, true).padStart(7);
|
||||
} else if (inputType === 'ac') {
|
||||
stimStr = num(point.stim, 3, false).padStart(8);
|
||||
calcStr = num(point.calc * scale, 3, false).padStart(7);
|
||||
calcStr = num(point.calc * oscale, 3, false).padStart(7);
|
||||
measStr = num(point.meas, 3, false).padStart(7);
|
||||
} else { // std
|
||||
if (/(^|[^A-Za-z])C\b/.test(inUnit || '') || /Temp/.test(inUnit || '')) {
|
||||
stimStr = num(point.stim, 2, true).padStart(8); // temperature input
|
||||
} else {
|
||||
const iscale = /mV|uV/.test(inUnit || '') ? 1000 : 1; // mV/uV input range (mA input is as-is)
|
||||
stimStr = num(point.stim * iscale, 3, true).padStart(8);
|
||||
}
|
||||
calcStr = num(point.calc * oscale, 3, true).padStart(7);
|
||||
measStr = num(point.meas * oscale, 3, true).padStart(7);
|
||||
}
|
||||
const errorStr = num(point.error, 3, true).padStart(8);
|
||||
return ' ' + stimStr + ' ' + calcStr + ' ' + measStr + ' ' + errorStr + ' ' + point.status;
|
||||
@@ -652,12 +665,12 @@ function generateExactDatasheet(record, specs) {
|
||||
: record.test_date || '';
|
||||
|
||||
let modelName = specs ? specs.MODNAME : record.model_number;
|
||||
// 7B header prepends "SCM" to the model name; templated SCM5B do the same (the spec
|
||||
// MODNAME is stored as "5B49-03" but the original prints "SCM5B49-03").
|
||||
if (family === 'SCM7B' && !modelName.toUpperCase().startsWith('SCM')) {
|
||||
modelName = 'SCM' + modelName;
|
||||
}
|
||||
if (tpl && family === 'SCM5B' && !modelName.toUpperCase().startsWith('SCM')) {
|
||||
// Mined templates carry the verbatim "Model:" name from the original (8B/5B prefix
|
||||
// the model name inconsistently — "SCM5B49-03" but "5B45-03D"), so use it directly.
|
||||
if (tpl && tpl.modelName) {
|
||||
modelName = tpl.modelName;
|
||||
} else if (family === 'SCM7B' && !modelName.toUpperCase().startsWith('SCM')) {
|
||||
// 7B header prepends "SCM" to the model name
|
||||
modelName = 'SCM' + modelName;
|
||||
}
|
||||
|
||||
@@ -720,12 +733,18 @@ function generateExactDatasheet(record, specs) {
|
||||
// (Hz) and AC (AAC/VAC) use the DSCA33/45 conventions (unsigned/integer stim, mA
|
||||
// scaling); standard voltage/current/temperature inputs use the legacy signed
|
||||
// formatter (sensor-type + maxIn from specs, which 8B/5B have).
|
||||
const accHdrStr = (tpl && Array.isArray(tpl.accHeader)) ? tpl.accHeader.join(' ') : '';
|
||||
const accInput = /\(Hz\)/.test(accHdrStr) ? 'freq'
|
||||
: (/AAC|VAC|mVAC/.test(accHdrStr) ? 'ac' : 'std');
|
||||
// Input/output units come from the accHeader column line ("<in> <out> <out>* ..."):
|
||||
// the input unit is the first parenthesised unit; the output unit is the one on the
|
||||
// measured column (the token ending in "*"). The miner's accOut is unreliable
|
||||
// ("?" for Iout models), so derive from the header.
|
||||
const accHdr1 = (tpl && Array.isArray(tpl.accHeader)) ? (tpl.accHeader[1] || '') : '';
|
||||
const accInput = /\(Hz\)/.test(accHdr1) ? 'freq'
|
||||
: (/AAC|VAC|mVAC/.test(accHdr1) ? 'ac' : 'std');
|
||||
const inUnit = (accHdr1.match(/\(([^)]*)\)/) || [])[1] || '';
|
||||
const outUnit = (accHdr1.match(/\(([^)]*)\)\*/) || [])[1] || (tpl && tpl.accOut) || '';
|
||||
for (const point of parsed.accuracy) {
|
||||
if (tpl && Array.isArray(tpl.accHeader) && (accInput === 'freq' || accInput === 'ac')) {
|
||||
lines.push(formatAccuracyLineDSCA3345(point, accInput, tpl.accOut));
|
||||
if (tpl && Array.isArray(tpl.accHeader)) {
|
||||
lines.push(formatAccuracyLineDSCA3345(point, accInput, outUnit, inUnit));
|
||||
continue;
|
||||
}
|
||||
lines.push(formatAccuracyLine(point, sensorNum, maxIn));
|
||||
@@ -1145,7 +1164,8 @@ function generateSCMVASDatasheet(record) {
|
||||
* bail for these. (Still gated on per-model `validated` inside generateExactDatasheet.)
|
||||
*/
|
||||
function rendersWithoutSpecs(modelNumber) {
|
||||
return !!DSCA3345_TEMPLATES[(modelNumber || '').trim()];
|
||||
const k = (modelNumber || '').trim();
|
||||
return !!(DSCA3345_TEMPLATES[k] || MINED_8B5BSCM[k]);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# DSCA Datasheet Fix 2 — STAGE 2 wire-in, STAGE 3 validator, publish of 68 clean models
|
||||
|
||||
## Update: 16:50 PT — converged 8B/5B/SCM onto the DSCA machinery; 69/136 validated; 2,348 published
|
||||
|
||||
Took on the 5070 8B/5B/SCM convergence (ref `8B5BSCM-RENDER-VERIFY`). Generalized the shared renderer
|
||||
so the per-model template path is no longer DSCA-only — 8B/SCM5B now route through the same
|
||||
templates/slotMap/fround/accuracy machinery (SCM7B excluded — separate parse7BRawData path). All
|
||||
behind the per-model `validated` gate; non-templated families byte-unchanged; **DSCA verified
|
||||
non-regressed throughout (DSCA33/45 held 54/56, staged-92 render).**
|
||||
|
||||
Key generalizations in `datasheet-exact.js`: load `8b5bscm-templates.json` (MINED_8B5BSCM); verbatim
|
||||
`ftHeader` (8B/5B use two Final-Test header schemes); accuracy formatter unified + unit-driven from
|
||||
the accHeader (miner's accOut is "?" for Iout models) — output mA/mV ->x1000, input mV/uV ->x1000,
|
||||
temp 2dp, std/freq signed / AC unsigned, Math.fround; `splitSpecUnit` splits after the last number so
|
||||
multi-word units survive ("uA RMS"); verbatim "Model:" name; `parseRawData` step-skip + footer 240VAC
|
||||
skip generalized to templated models; `rendersWithoutSpecs` covers mined 8B/5B (54 lacked specs).
|
||||
Tools: `enrich-8b5bscm.js` (ftHeader+modelName), `validate-mined.js`, `slotmap-from-hoffman.js`
|
||||
(parameterized via DSCA_TPL), `publish-mined-gap.js`.
|
||||
|
||||
Validation vs live Hoffman (iterated 37 -> 56 -> 59 -> 64 -> 69): **69/136 validated** — 24 8B + 21
|
||||
SCM5B via the new template path, 24 SCM7B already correct via the unchanged legacy path. Published the
|
||||
gap absent-only (probed 2,514 unuploaded serials, 2,440 absent): **created=2348 updated=0 unchanged=0
|
||||
errors=9 skipped=83** — zero overwrites of pristine originals. Commits `9642de30`, `6260b5a4`,
|
||||
`1c630552`.
|
||||
|
||||
Remaining 67 (gated null, safe): 12 need slotMaps the Hoffman oracle couldn't derive; the rest are a
|
||||
family-divergent tail — 8B/5B Final-Test DROPS the sign on negative measured values (opposite of
|
||||
DSCA's keep-sign, needs family-specific measured formatting) and per-model "Packing Check List" footer
|
||||
variants. Well-characterized for continuation.
|
||||
|
||||
## Update: 14:00 PT — DSCA33/45 accuracy-data reverse-engineered; 54/56 validated; 1,452 published
|
||||
|
||||
Picked up the 5070 Hoffman-recovery handoff and finished DSCA33/45 end-to-end. After wiring the
|
||||
|
||||
@@ -20,6 +20,8 @@ function req(m, uri, h, b) { return new Promise((res, rej) => { const u = new UR
|
||||
const fi = L.findIndex(l => /FINAL TEST RESULTS/.test(l)); if (fi < 0) continue;
|
||||
const hi = L.findIndex((l, i) => i > fi && /Parameter\s+Measured/.test(l)); if (hi < 0) continue;
|
||||
tpl[m].ftHeader = L[hi];
|
||||
const mn = (g.Content.match(/^\s*Model:\s*(.+?)\s*$/m) || [])[1];
|
||||
if (mn) tpl[m].modelName = mn;
|
||||
const star = /Measured Value\*/.test(L[hi]) ? 'star' : 'plain';
|
||||
schemes[star] = (schemes[star] || 0) + 1;
|
||||
done++;
|
||||
|
||||
41
projects/dataforth-dos/tools/publish-mined-gap.js
Normal file
41
projects/dataforth-dos/tools/publish-mined-gap.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// Publish the DSCA33/45 gap SAFELY: for each validated model's unuploaded PASS serial,
|
||||
// GET the Hoffman record; push ONLY those that are absent (404) so every push is a
|
||||
// Created — never an UPDATE that could overwrite a pristine original. (The inventory
|
||||
// file is stale, so we probe per-serial instead of trusting api_uploaded_at.)
|
||||
const fs = require('fs'), https = require('https');
|
||||
const db = require('./database/db');
|
||||
const { uploadBySerialNumbers } = require('./database/upload-to-api');
|
||||
const tpl = require(process.env.DSCA_TPL || './dsca33-45-templates.json');
|
||||
const c = JSON.parse(fs.readFileSync('C:\\ProgramData\\dataforth-uploader\\credentials.json', 'utf8'));
|
||||
const DRY = !process.argv.includes('--push');
|
||||
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', () => res({ status: x.statusCode, body: d })); }); r.on('error', rej); r.on('timeout', () => r.destroy(new Error('timeout'))); if (b) r.write(b); r.end(); }); }
|
||||
async function token() { 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 r = await req('POST', c.CF_TOKEN_URL, { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(form) }, form); return JSON.parse(r.body).access_token; }
|
||||
(async () => {
|
||||
const validated = Object.keys(tpl).filter(m => tpl[m].validated);
|
||||
const ph = validated.map((_, i) => '$' + (i + 1)).join(',');
|
||||
const rows = await db.query(`SELECT serial_number FROM test_records WHERE overall_result='PASS' AND api_uploaded_at IS NULL AND model_number IN (${ph}) ORDER BY serial_number`, validated);
|
||||
const sns = rows.map(r => r.serial_number);
|
||||
console.log(`validated models: ${validated.length}; unuploaded PASS serials to probe: ${sns.length}`);
|
||||
const t = await token();
|
||||
const absent = [], present = [];
|
||||
for (let i = 0; i < sns.length; i++) {
|
||||
const r = await req('GET', c.CF_API_BASE + '/api/v1/TestReportDataFiles/' + encodeURIComponent(sns[i]), { Authorization: 'Bearer ' + t });
|
||||
if (r.status === 404 || (r.status === 200 && !/"Content"/.test(r.body))) absent.push(sns[i]);
|
||||
else if (r.status === 200) present.push(sns[i]);
|
||||
else absent.push(sns[i]); // treat unknown as absent? no — be safe: skip
|
||||
if ((i + 1) % 200 === 0) console.log(` probed ${i + 1}/${sns.length} absent=${absent.length} present=${present.length}`);
|
||||
}
|
||||
console.log(`\nPROBE DONE: absent(not on Hoffman)=${absent.length} present(already live)=${present.length}`);
|
||||
if (DRY) { console.log('\n(dry run — pass --push to Created-publish the absent set)'); await db.close(); return; }
|
||||
console.log('\nPublishing absent set (Created only)...');
|
||||
const tot = { created: 0, updated: 0, unchanged: 0, errors: 0, skipped: 0 };
|
||||
const CH = 500;
|
||||
for (let i = 0; i < absent.length; i += CH) {
|
||||
const r = await uploadBySerialNumbers(absent.slice(i, i + CH));
|
||||
for (const k of Object.keys(tot)) tot[k] += r[k] || 0;
|
||||
console.log(` ${Math.min(i + CH, absent.length)}/${absent.length} cumulative ${JSON.stringify(tot)}`);
|
||||
}
|
||||
console.log('\nDONE ' + JSON.stringify(tot));
|
||||
if (tot.updated > 0) console.log('[WARNING] ' + tot.updated + ' UPDATED — investigate (should be 0 for an absent-only push)');
|
||||
await db.close();
|
||||
})().catch(e => { console.error('ERR', e.message, e.stack); process.exit(1); });
|
||||
Reference in New Issue
Block a user