Add SCMVAS/SCMHVAS datasheet pipeline extension (Dataforth)

Extends the Test Datasheet Pipeline on AD2:C:\Shares\testdatadb to
generate web-published datasheets for the SCMVAS-Mxxx (obsolete) and
SCMHVAS-Mxxxx (replacement) High Voltage Input Module product lines.
Both are tested either with the existing TESTHV3 software (production
VASLOG .DAT logs) or in Engineering with plain .txt output.

Key changes on AD2 (all deployed 2026-04-12 with dated backups):

- parsers/spec-reader.js: getSpecs() returns a `{_family:'SCMVAS',
  _noSpecs:true}` sentinel for SCMVAS/SCMHVAS/VAS-M/HVAS-M model prefixes
  so the export pipeline does not silently skip them for missing specs.
- templates/datasheet-exact.js: new Accuracy-only template branch
  (generateSCMVASDatasheet + helpers) that mirrors the existing shipped
  format byte-for-byte. Extraction regex covers both QuickBASIC STR$()
  output formats: scientific-with-trailing-status-digit (98.4% of
  records) and plain-decimal (1.6% of records above QB's threshold).
- parsers/vaslog-engtxt.js (new): parses the Engineering-Tested .txt
  files in TS-3R\LOGS\VASLOG\VASLOG - Engineering Tested\. Filename SN
  regex strips optional trailing 14-digit timestamp; in-file "SN:"
  header is the authoritative source when the filename is malformed.
- database/import.js: LOG_TYPES grows a VASLOG_ENG entry with
  subfolder + recursive flags. Pre-existing 7 log types keep their
  implicit recursive=true behaviour (config.recursive !== false).
  importFiles() routes VASLOG_ENG paths before the generic loop so a
  VASLOG - Engineering Tested/*.txt path does not mis-dispatch to the
  multiline parser.
- database/export-datasheets.js: VASLOG_ENG records are written
  verbatim via fs.copyFileSync(source_file, For_Web/<SN>.TXT) for true
  byte-level pass-through, with a graceful raw_data fallback when the
  source file is no longer on disk.

Deploy outcome:
- 27,503 SCMVAS/SCMHVAS datasheets rendered (27,065 from scientific +
  438 from plain-decimal PASS lines, post-patch rerun)
- 434 Engineering-Tested .txt files pass-through-copied to For_Web
- 0 errors across both batches

Repo layout added here:
- scmvas-hvas-research/: discovery artifacts (source .BAS, hvin.dat,
  sample .DAT + .txt, binary-format notes, IMPLEMENTATION_PLAN.md)
- implementation/: staged final code + deploy helpers + local test
  harness + per-step verification scripts
- backups/pre-deploy-20260412/: independent local snapshot of the 4
  AD2 files replaced, pulled byte-for-byte before deploy

All helper scripts fetch the AD2 password at runtime from the SOPS
vault (clients/dataforth/ad2.sops.yaml). None of the committed files
contain the plaintext credential. Known vault-entry hygiene issue
(stale shell-escape backslash before the `!`) is documented in the
fetcher comments and stripped at read-time; flagged separately for
cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 17:02:20 -07:00
parent 499fd5d01a
commit 45083f4735
114 changed files with 35486 additions and 0 deletions

View File

@@ -0,0 +1,198 @@
# SCMVAS/SCMHVAS Datasheet Pipeline Integration — Implementation Plan
**Created:** 2026-04-12
**Basis:** Discovery + sample analysis completed 2026-04-11
**Target environment:** AD2 server, `C:\Shares\testdatadb\`
**Decision:** Option C — simple Accuracy-only datasheet, generated directly from DB record, no `hvin.dat` lookup needed
---
## Scope
Two product families need first-class support in the automated datasheet pipeline:
- **SCMVAS-Mxxx** — obsolete, datasheets end ~2024 plus occasional retests
- **SCMHVAS-Mxxxx** — replacement line (two test paths):
- Production half → TESTHV3 software → logs at `TS-3R\LOGS\VASLOG\*.DAT` (multiline CSV format, same as 5BLOG/8BLOG)
- Engineering half → plain `.txt` output pre-rendered at `TS-3R\LOGS\VASLOG\VASLOG - Engineering Tested\*.txt`
### Sample datasheet format (the exact output we must produce)
```
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 10/04/2023
Model: SCMHVAS-M0200
SN: 166590-1
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.007% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.
```
Each line prefixed with 8 spaces. Tilde separator line is 71 tildes. Specification string is constant `+/- 0.03%`. Check List uses `__X__` markers (pre-filled, not blank like SCM7B).
---
## Accuracy extraction rule (production VASLOG .DAT)
The raw_data PASS/FAIL line looks like `"PASS-7.005501E-033"` or `"PASS 5.999184E-033"`. Format is:
```
<"PASS"|"FAIL"> <optional space> <signed-float-7digits> E - <2-digit-exponent> <trailing-status-digit>
```
The trailing single digit (observed: `2` or `3`) is a test-status code, NOT part of the float. The captured float is **already in percent units** (not a fraction).
**Extraction regex:** `/^(PASS|FAIL)\s*(-?\d+\.?\d*E[+-]?\d{2})\d?$/i` applied to the stripped contents of the quoted status string.
**Formatting:** abs value, 3 decimals, trim trailing zeros → display like `0.007%`, `0.01%`, `0.005%`.
Verified against samples:
- `"PASS-7.005501E-033"``7.005501E-03` = `0.007005501%` → display `0.007%`
- `"PASS 4.988443E-033"``4.988443E-03` = `0.004988443%` → display `0.005%`
- `"PASS 1.524978E-023"``1.524978E-02` = `0.01524978%` → display `0.015%`
---
## Changes by file
### 1. `parsers/spec-reader.js`
**Goal:** allow SCMVAS/SCMHVAS model numbers to pass the MODNAME validation filter so they land in the spec map (with a synthetic no-specs stub), OR bypass spec lookup entirely for this family.
**Approach:** bypass entirely. Cleaner — no stub records.
Change `getSpecs()` to special-case SCMVAS/SCMHVAS and return a well-known "no-specs" sentinel (e.g. `{ _family: 'SCMVAS', _noSpecs: true }`) instead of `null`. This lets `exportNewRecords()` proceed to formatter without silently skipping.
Also update the MODNAME prefix regex at line 287 (`^(SCM5B|5B|SCM7B|7B|8B|DSCA|DSCT|SCT|BOGUS)`) — this line rejects records in the binary DAT parser only, which doesn't affect VASLOG (VASLOG isn't read through that code path). No change needed here — leaving SCMVAS/SCMHVAS out of the binary parser filter is correct since we don't parse `hvin.dat`.
**Diff scope:** ~20 lines in `getSpecs()`.
### 2. `templates/datasheet-exact.js`
**Goal:** new family branch emitting the simple Accuracy-only template.
**Approach:** Add `SCMVAS` to DATA_LINES (single-entry array: `[['Accuracy', '%']]`). At the top of `generateExactDatasheet()`, if the spec stub flags `_family === 'SCMVAS'`, route to a dedicated `generateSCMVASDatasheet(record)` helper that builds the 35-line template above. This helper does NOT use `specs` — only `record.model_number`, `record.serial_number`, `record.test_date`, `record.overall_result`, and `record.raw_data`.
The helper must:
- Render 8-space left indent on every line
- Date formatted `MM/DD/YYYY` (matching newer samples) — note: "Corrected HVAS" uses `MM-DD-YYYY`; use `MM/DD/YYYY` per the most recent Engineering-Tested samples
- Extract accuracy value via the regex above
- Constants: specification = `+/- 0.03%`, withstand/Hi-Pot block omitted (SCMVAS has none), checklist uses `__X__` markers
Also delete the vestigial `startsWith('SCMHVAS')` check at existing line 652 (it was inside the DSCT branch and is no longer reachable once SCMVAS gets its own branch).
**Diff scope:** ~80 new lines (new helper + DATA_LINES entry + router change + one deletion).
### 3. `database/export-datasheets.js`
**Goal:** do not skip SCMVAS/SCMHVAS records due to missing specs.
**Approach:** after changing `getSpecs()` to return a stub for this family, the existing `if (!specs) continue;` logic in both `run()` and `exportNewRecords()` just works. No explicit change needed — verify only.
### 4. `database/import.js`
**Goal:** ingest the Engineering-Tested plain `.txt` files.
**Approach:** Add a new dedicated import branch for the `VASLOG - Engineering Tested` subfolder:
- Add a new parser `parsers/vaslog-engtxt.js` that:
- Takes a `.txt` filepath, parses SN from filename (pattern `^(\d+-\d+[A-Za-z]?)(?:\d{14})?\.txt$`, capturing the SN segment like `166590-1` or `167601-4` — the optional 14-digit timestamp suffix `MMDDYYYYhhmmss` is dropped)
- Reads the file, extracts `Model:`, `Date:`, `SN:`, `Accuracy`, and `Status` from the plain-text header rows
- Returns one record with `log_type='VASLOG_ENG'`, `overall_result='PASS'` (or derive from the Status field), `raw_data=<full file contents>`, `source_file=<path>`
- Register `VASLOG_ENG` in the LOG_TYPES map with a new `vaslog-engtxt` parser alias
- Make the `importStationLogs()` walk recurse into `VASLOG/` one level to pick up the `VASLOG - Engineering Tested/*.txt` subfolder. Cleanest: parameterize the LOG_TYPES entry with `subfolder` and `recursive` flags.
For the **pass-through copy to `X:\For_Web\<SN>.TXT`**, the Engineering-Tested files already have the correct final format. Two sub-options:
- **(4a) Pass-through**: `exportNewRecords()` detects `log_type === 'VASLOG_ENG'`, copies `raw_data` (the original file contents) verbatim into `X:\For_Web\<SN>.TXT`, sets `forweb_exported_at`. Zero risk of format drift.
- **(4b) Re-render**: treat the `VASLOG_ENG` record the same as a VASLOG record — run it through the same `generateSCMVASDatasheet()` helper. Consistent with production path.
**Recommendation: (4a) pass-through.** Reasons:
- The files already match the target format exactly (verified by comparing samples to `Corrected HVAS Files/*.txt`)
- Preserves any Engineering-hand-tweaked formatting
- If drift is ever needed, switching to (4b) is a one-line change later
**Diff scope:** new `parsers/vaslog-engtxt.js` (~60 lines) + ~30 lines across `import.js` + ~15 lines in `export-datasheets.js` for the pass-through branch.
### 5. `C:\Shares\test\scripts\Sync-FromNAS-rsync.ps1`
**Goal:** ensure the Engineering-Tested subfolder is included in the sync.
**Approach:** Verify that the existing `TS-3R\LOGS\` rsync already pulls the full subtree (`--recursive`). Based on prior session logs, the rsync syncs `TS-3R/*`, so the subfolder likely rides along. **Verify only — no change expected.** If rsync uses explicit includes, add `VASLOG - Engineering Tested/***` to the include list.
### 6. Database schema
**Goal:** no schema changes required.
The `test_records` table already has `log_type`, `model_number`, `serial_number`, `test_date`, `overall_result`, `raw_data`, `source_file`, `forweb_exported_at`. The new `VASLOG_ENG` log_type is just a new string value in an existing column.
### 7. Backfill strategy
**Production VASLOG .DAT**: these are already imported into `test_records` via the existing multiline parser. After the spec-reader/formatter changes deploy, run:
```
node database/export-datasheets.js --limit 0
```
to regenerate datasheets for all PASS SCMVAS/SCMHVAS records where `forweb_exported_at IS NULL`. This backfills historical SCMVAS/SCMHVAS records that were previously skipped due to "no specs".
**Engineering-Tested .txt**: run the full import once after the new parser is added. Should pick up all 434 existing files and copy them to `X:\For_Web\`.
---
## Risks / edge cases
1. **The `VAS-MPT.DAT` / `HVAS-MPT.DAT` "pass-through" models** — might need a slightly different treatment (skip Check List? different wording?). Treat same as regular SCMVAS for now; revisit if user reports a mismatch.
2. **FAIL records** — the PASS regex above also matches `FAIL`. Verify the Status column in the output shows `FAIL` and that the existing `exportNewRecords` logic (which filters `overall_result = 'PASS'`) skips FAIL datasheets by default. No action needed.
3. **Filename SN extraction for Engineering-Tested** — observed patterns: `166590-1.txt`, `166590-110042023104524.txt` (trailing timestamp). Regex must correctly split the timestamp. A small number of edge cases exist (e.g. `166594-1010042023090444.txt` = SN `166594-10`, timestamp `10042023090444`) — the SN has variable-length second segment. Safe rule: SN ends at the last `-<digits>` segment before the optional 14-digit timestamp.
4. **Duplicate files**`166593-4.txt` (1519 bytes) and `166593-410042023114928.txt` (1600 bytes) coexist. Treat the timestamped filename as canonical; untimestamped is a later re-render. Import both but dedupe on `(log_type, model_number, serial_number, test_date, test_station)` (existing unique constraint already handles this).
5. **Date format variance** — production VASLOG stores `MM-DD-YYYY` in raw_data; Engineering-Tested `.txt` uses `MM/DD/YYYY` or `MM-DD-YYYY` depending on vintage. Normalize all date displays to `MM/DD/YYYY` per the newest Engineering-Tested output.
---
## Test plan (Coding Agent must verify)
Before declaring complete:
1. `node database/export-datasheets.js --dry-run --serial 179379-1` → should preview a well-formed SCMHVAS datasheet (no "missing specs" skip).
2. `node database/export-datasheets.js --serial 166590-1` → compare generated `X:\For_Web\166590-1.TXT` byte-for-byte against the existing `samples/vaslog-engtxt/166590-110042023104524.txt`. Expect visual match; char-level drift acceptable only in whitespace.
3. Full incremental import of the `VASLOG - Engineering Tested` subfolder → verify all 434 `.txt` files copy to `X:\For_Web\`.
4. Historical backfill of production VASLOG records → spot-check 5 SCMHVAS and 5 SCMVAS datasheets against any known-good reference in `Corrected HVAS Files/`.
5. Regression: pick 10 existing SCM5B + 10 DSCA datasheets, regenerate, confirm no format drift vs. their current `X:\For_Web\*.TXT`.
---
## Delegation
Once this plan is approved, hand off to the Coding Agent with:
- This plan as the spec
- All research artifacts under `projects/dataforth-dos/datasheet-pipeline/scmvas-hvas-research/`
- Sample output at `samples/corrected-hvas/171087-1.txt` and `samples/vaslog-engtxt/166590-110042023104524.txt` as golden references
- Access to AD2 via `paramiko` (creds from vault path `clients/dataforth/ad2.sops.yaml`)
After implementation, mandatory Code Review Agent pass before deploying to `C:\Shares\testdatadb\`.

View File

@@ -0,0 +1,132 @@
/**
* Archive For_Web Files
*
* Moves files older than the current year into year-based subfolders.
* e.g., X:\For_Web\2024\12345-1.TXT
*
* The TestDataSheetUploader only uploads files modified in the current year,
* so archived files won't be re-uploaded. Keeps the active folder small and fast.
*
* Usage:
* node archive-for-web.js Archive all pre-current-year files
* node archive-for-web.js --dry-run Show what would be moved
* node archive-for-web.js --year 2024 Only archive files from 2024
*/
const fs = require('fs');
const path = require('path');
const FOR_WEB = 'X:\\For_Web';
function run() {
const args = process.argv.slice(2);
const dryRun = args.includes('--dry-run');
const yearIdx = args.indexOf('--year');
const targetYear = yearIdx >= 0 ? parseInt(args[yearIdx + 1]) : null;
const currentYear = new Date().getFullYear();
console.log('========================================');
console.log('Archive For_Web Files');
console.log('========================================');
console.log(`Source: ${FOR_WEB}`);
console.log(`Current year: ${currentYear}`);
console.log(`Dry run: ${dryRun}`);
if (targetYear) console.log(`Target year: ${targetYear}`);
console.log(`Start: ${new Date().toISOString()}`);
console.log('');
if (!fs.existsSync(FOR_WEB)) {
console.error('ERROR: For_Web directory not found');
process.exit(1);
}
// Scan files
console.log('Scanning files...');
const entries = fs.readdirSync(FOR_WEB, { withFileTypes: true });
const yearCounts = {};
let scanned = 0;
let toMove = 0;
let moved = 0;
let errors = 0;
for (const entry of entries) {
if (!entry.isFile()) continue;
scanned++;
if (scanned % 50000 === 0) {
process.stdout.write(`\rScanned: ${scanned}`);
}
const filePath = path.join(FOR_WEB, entry.name);
let stat;
try {
stat = fs.statSync(filePath);
} catch (err) {
continue;
}
const fileYear = stat.mtime.getFullYear();
// Skip current year files
if (fileYear >= currentYear) continue;
// If targeting a specific year, skip others
if (targetYear && fileYear !== targetYear) continue;
yearCounts[fileYear] = (yearCounts[fileYear] || 0) + 1;
toMove++;
if (!dryRun) {
// Create year subdirectory if needed
const yearDir = path.join(FOR_WEB, String(fileYear));
if (!fs.existsSync(yearDir)) {
fs.mkdirSync(yearDir);
console.log(`\nCreated directory: ${yearDir}`);
}
const destPath = path.join(yearDir, entry.name);
try {
fs.renameSync(filePath, destPath);
moved++;
} catch (err) {
// If rename fails (cross-device), try copy+delete
try {
fs.copyFileSync(filePath, destPath);
fs.unlinkSync(filePath);
moved++;
} catch (err2) {
console.error(`\nERROR moving ${entry.name}: ${err2.message}`);
errors++;
}
}
if (moved % 10000 === 0) {
process.stdout.write(`\rMoved: ${moved}`);
}
}
}
console.log('\n');
console.log('========================================');
console.log('Archive Summary');
console.log('========================================');
console.log(`Files scanned: ${scanned}`);
console.log(`Files to archive: ${toMove}`);
if (Object.keys(yearCounts).length > 0) {
console.log('\nBy year:');
for (const [year, count] of Object.entries(yearCounts).sort()) {
console.log(` ${year}: ${count.toLocaleString()} files`);
}
}
if (!dryRun) {
console.log(`\nFiles moved: ${moved}`);
console.log(`Errors: ${errors}`);
}
console.log(`\nEnd: ${new Date().toISOString()}`);
}
run();

View File

@@ -0,0 +1,139 @@
/**
* PostgreSQL Database Abstraction Layer
*
* Provides a connection pool and helper methods for the TestDataDB app.
* Replaces better-sqlite3 singleton with pg.Pool.
*
* Environment variables (all optional, defaults connect to local PG):
* PGHOST (default: localhost)
* PGPORT (default: 5432)
* PGUSER (default: testdatadb_app)
* PGPASSWORD (default: DfTestDB2026!)
* PGDATABASE (default: testdatadb)
*/
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.PGHOST || 'localhost',
port: parseInt(process.env.PGPORT || '5432', 10),
user: process.env.PGUSER || 'testdatadb_app',
password: process.env.PGPASSWORD || 'DfTestDB2026!',
database: process.env.PGDATABASE || 'testdatadb',
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
});
pool.on('error', (err) => {
console.error(`[${new Date().toISOString()}] [PG POOL ERROR] ${err.message}`);
});
/**
* Convert SQLite-style ? placeholders to PostgreSQL $1, $2, ... placeholders.
* Skips ? inside single-quoted strings.
*/
function convertPlaceholders(sql) {
let idx = 0;
let inString = false;
let result = '';
for (let i = 0; i < sql.length; i++) {
const ch = sql[i];
if (ch === "'" && (i === 0 || sql[i - 1] !== '\\')) {
inString = !inString;
result += ch;
} else if (ch === '?' && !inString) {
idx++;
result += '$' + idx;
} else {
result += ch;
}
}
return result;
}
/**
* Execute a query, return all rows.
* @param {string} sql - SQL with ? or $N placeholders
* @param {Array} params - Parameter values
* @returns {Promise<Array>} rows
*/
async function query(sql, params = []) {
const pgSql = sql.includes('?') ? convertPlaceholders(sql) : sql;
const result = await pool.query(pgSql, params);
return result.rows;
}
/**
* Execute a query, return the first row or null.
*/
async function queryOne(sql, params = []) {
const rows = await query(sql, params);
return rows[0] || null;
}
/**
* Execute a statement (INSERT/UPDATE/DELETE), return { rowCount }.
*/
async function execute(sql, params = []) {
const pgSql = sql.includes('?') ? convertPlaceholders(sql) : sql;
const result = await pool.query(pgSql, params);
return { rowCount: result.rowCount, rows: result.rows };
}
/**
* Run a function inside a transaction.
* The callback receives a client with query/execute helpers.
* @param {Function} fn - async (client) => result
* @returns {Promise<*>} result of fn
*/
async function transaction(fn) {
const client = await pool.connect();
try {
await client.query('BEGIN');
const txClient = {
async query(sql, params = []) {
const pgSql = sql.includes('?') ? convertPlaceholders(sql) : sql;
const result = await client.query(pgSql, params);
return result.rows;
},
async queryOne(sql, params = []) {
const rows = await txClient.query(sql, params);
return rows[0] || null;
},
async execute(sql, params = []) {
const pgSql = sql.includes('?') ? convertPlaceholders(sql) : sql;
const result = await client.query(pgSql, params);
return { rowCount: result.rowCount, rows: result.rows };
},
// Direct pg client access for COPY or other advanced operations
raw: client,
};
const result = await fn(txClient);
await client.query('COMMIT');
return result;
} catch (err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release();
}
}
/**
* Close the pool (for graceful shutdown).
*/
async function close() {
await pool.end();
}
/**
* Get the raw pool (for advanced use like COPY).
*/
function getPool() {
return pool;
}
module.exports = { query, queryOne, execute, transaction, close, getPool, convertPlaceholders };

View File

@@ -0,0 +1,216 @@
/**
* Export Datasheets
*
* Generates TXT datasheets for unexported PASS records and writes them to X:\For_Web\.
* Updates forweb_exported_at after successful export.
*
* Usage:
* node export-datasheets.js Export all pending (batch mode)
* node export-datasheets.js --limit 100 Export up to 100 records
* node export-datasheets.js --file <paths> Export records matching specific source files
* node export-datasheets.js --serial 178439-1 Export a specific serial number
* node export-datasheets.js --dry-run Show what would be exported without writing
*/
const fs = require('fs');
const path = require('path');
const db = require('./db');
const { loadAllSpecs, getSpecs } = require('../parsers/spec-reader');
const { generateExactDatasheet } = require('../templates/datasheet-exact');
// Configuration
const OUTPUT_DIR = 'X:\\For_Web';
const BATCH_SIZE = 500;
async function run() {
const args = process.argv.slice(2);
const dryRun = args.includes('--dry-run');
const limitIdx = args.indexOf('--limit');
const limit = limitIdx >= 0 ? parseInt(args[limitIdx + 1]) : 0;
const serialIdx = args.indexOf('--serial');
const serial = serialIdx >= 0 ? args[serialIdx + 1] : null;
const fileIdx = args.indexOf('--file');
const files = fileIdx >= 0 ? args.slice(fileIdx + 1).filter(f => !f.startsWith('--')) : null;
console.log('========================================');
console.log('Datasheet Export');
console.log('========================================');
console.log(`Output: ${OUTPUT_DIR}`);
console.log(`Dry run: ${dryRun}`);
if (limit) console.log(`Limit: ${limit}`);
if (serial) console.log(`Serial: ${serial}`);
console.log(`Start: ${new Date().toISOString()}`);
if (!dryRun && !fs.existsSync(OUTPUT_DIR)) {
console.error(`ERROR: Output directory does not exist: ${OUTPUT_DIR}`);
process.exit(1);
}
console.log('\nLoading model specs...');
const specMap = loadAllSpecs();
// Build query
const conditions = [`overall_result = 'PASS'`, `forweb_exported_at IS NULL`];
const params = [];
let paramIdx = 0;
if (serial) {
paramIdx++;
conditions.push(`serial_number = $${paramIdx}`);
params.push(serial);
}
if (files && files.length > 0) {
const placeholders = files.map(() => { paramIdx++; return `$${paramIdx}`; }).join(',');
conditions.push(`source_file IN (${placeholders})`);
params.push(...files);
}
let sql = `SELECT * FROM test_records WHERE ${conditions.join(' AND ')} ORDER BY test_date DESC`;
if (limit) {
paramIdx++;
sql += ` LIMIT $${paramIdx}`;
params.push(limit);
}
const records = await db.query(sql, params);
console.log(`\nFound ${records.length} records to export`);
if (records.length === 0) {
console.log('Nothing to export.');
await db.close();
return { exported: 0, skipped: 0, errors: 0 };
}
let exported = 0;
let skipped = 0;
let errors = 0;
let noSpecs = 0;
let pendingUpdates = [];
for (const record of records) {
try {
const specs = getSpecs(specMap, record.model_number);
if (!specs) {
noSpecs++;
skipped++;
continue;
}
const txt = generateExactDatasheet(record, specs);
if (!txt) {
skipped++;
continue;
}
const filename = record.serial_number + '.TXT';
const outputPath = path.join(OUTPUT_DIR, filename);
if (dryRun) {
console.log(` [DRY RUN] Would write: ${filename}`);
exported++;
} else {
fs.writeFileSync(outputPath, txt, 'utf8');
pendingUpdates.push(record.id);
exported++;
// Batch commit
if (pendingUpdates.length >= BATCH_SIZE) {
await flushUpdates(pendingUpdates);
pendingUpdates = [];
process.stdout.write(`\r Exported: ${exported} / ${records.length}`);
}
}
} catch (err) {
console.error(`\n ERROR exporting ${record.serial_number}: ${err.message}`);
errors++;
}
}
// Flush remaining updates
if (pendingUpdates.length > 0) {
await flushUpdates(pendingUpdates);
}
console.log(`\n\n========================================`);
console.log(`Export Complete`);
console.log(`========================================`);
console.log(`Exported: ${exported}`);
console.log(`Skipped: ${skipped} (${noSpecs} missing specs)`);
console.log(`Errors: ${errors}`);
console.log(`End: ${new Date().toISOString()}`);
await db.close();
return { exported, skipped, errors };
}
async function flushUpdates(ids) {
const now = new Date().toISOString();
await db.transaction(async (txClient) => {
for (const id of ids) {
await txClient.execute(
'UPDATE test_records SET forweb_exported_at = $1 WHERE id = $2',
[now, id]
);
}
});
}
// Export function for use by import.js (no db argument -- uses shared pool)
async function exportNewRecords(specMap, filePaths) {
if (!fs.existsSync(OUTPUT_DIR)) {
console.log(`[EXPORT] Output directory not found: ${OUTPUT_DIR}`);
return 0;
}
const conditions = [`overall_result = 'PASS'`, `forweb_exported_at IS NULL`];
const params = [];
let paramIdx = 0;
if (filePaths && filePaths.length > 0) {
const placeholders = filePaths.map(() => { paramIdx++; return `$${paramIdx}`; }).join(',');
conditions.push(`source_file IN (${placeholders})`);
params.push(...filePaths);
}
const sql = `SELECT * FROM test_records WHERE ${conditions.join(' AND ')}`;
const records = await db.query(sql, params);
if (records.length === 0) return 0;
let exported = 0;
await db.transaction(async (txClient) => {
for (const record of records) {
const specs = getSpecs(specMap, record.model_number);
if (!specs) continue;
const txt = generateExactDatasheet(record, specs);
if (!txt) continue;
const filename = record.serial_number + '.TXT';
const outputPath = path.join(OUTPUT_DIR, filename);
try {
fs.writeFileSync(outputPath, txt, 'utf8');
await txClient.execute(
'UPDATE test_records SET forweb_exported_at = $1 WHERE id = $2',
[new Date().toISOString(), record.id]
);
exported++;
} catch (err) {
console.error(`[EXPORT] Error writing ${filename}: ${err.message}`);
}
}
});
console.log(`[EXPORT] Generated ${exported} datasheet(s)`);
return exported;
}
if (require.main === module) {
run().catch(console.error);
}
module.exports = { exportNewRecords };

View File

@@ -0,0 +1,152 @@
/**
* Generate PDF datasheets for specific serial numbers
* For Quatronix customer request - 70 datasheets needed urgently
*/
const fs = require('fs');
const path = require('path');
const Database = require('better-sqlite3');
const PDFDocument = require('pdfkit');
const { loadAllSpecs, getSpecs } = require('../parsers/spec-reader');
const { generateExactDatasheet } = require('../templates/datasheet-exact');
const DB_PATH = path.join(__dirname, 'testdata.db');
const OUTPUT_DIR = process.argv[2] || path.join(process.env.USERPROFILE, 'Desktop', 'Quatronix-Datasheets');
// Build the list of needed serial numbers
const needed = [
// SCM5B34-03: 177368-6~15
...Array.from({length:10}, (_,i) => '177368-' + (i+6)),
// SCM5B35-02: 177625-6~10
...Array.from({length:5}, (_,i) => '177625-' + (i+6)),
// SCM5B38-05: 177963-6
'177963-6',
// SCM5B392-11: 177199-13
'177199-13',
// SCM5B40-03: 178444-1
'178444-1',
// SCM5B41-02: 178362-1
'178362-1',
// SCM5B42-02: 177299-4, 177299-5
'177299-4', '177299-5',
// SCM5B45-02D: 178607-1
'178607-1',
// SCM5B45-04: 178385-4~8
...Array.from({length:5}, (_,i) => '178385-' + (i+4)),
// SCM5B48-01: 177593-1
'177593-1',
// SCM5B49-05: 177000-15
'177000-15',
// DSCA30-05C: 176566-2
'176566-2',
// DSCA38-19C: 178001-22, 178001-23
'178001-22', '178001-23',
// DSCA41-02: 178135-2
'178135-2',
// DSCA38-1468: 178595-1
'178595-1',
// SCM5B41-02: 177012-1~30
...Array.from({length:30}, (_,i) => '177012-' + (i+1)),
// SCM5B47S-10: 178768-8
'178768-8',
// SCM5B45-04D: 177207-4~7
...Array.from({length:4}, (_,i) => '177207-' + (i+4)),
// 8B51-12: 178601-6~9
...Array.from({length:4}, (_,i) => '178601-' + (i+6)),
];
async function generatePdf(txt, outputPath) {
return new Promise((resolve, reject) => {
const doc = new PDFDocument({
size: 'LETTER',
margins: { top: 36, bottom: 36, left: 36, right: 36 }
});
const stream = fs.createWriteStream(outputPath);
stream.on('finish', resolve);
stream.on('error', reject);
doc.pipe(stream);
doc.font('Courier').fontSize(9.5);
const lines = txt.split(/\r?\n/);
for (const line of lines) {
doc.text(line, { lineGap: 1 });
}
doc.end();
});
}
async function run() {
console.log('========================================');
console.log('Generate Customer PDFs');
console.log('========================================');
console.log(`Output: ${OUTPUT_DIR}`);
console.log(`Serial numbers: ${needed.length}`);
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
const specMap = loadAllSpecs();
const db = new Database(DB_PATH, { readonly: true });
let generated = 0;
let notFound = [];
let noSpecs = [];
let errors = [];
for (const sn of needed) {
const record = db.prepare(
"SELECT * FROM test_records WHERE serial_number = ? AND overall_result = 'PASS' LIMIT 1"
).get(sn);
if (!record) {
notFound.push(sn);
continue;
}
const specs = getSpecs(specMap, record.model_number);
if (!specs) {
noSpecs.push(sn + ' (' + record.model_number + ')');
continue;
}
const txt = generateExactDatasheet(record, specs);
if (!txt) {
errors.push(sn + ' (format failed)');
continue;
}
// Write TXT
fs.writeFileSync(path.join(OUTPUT_DIR, sn + '.TXT'), txt, 'utf8');
// Write PDF
try {
await generatePdf(txt, path.join(OUTPUT_DIR, sn + '.pdf'));
generated++;
process.stdout.write(`\rGenerated: ${generated}`);
} catch (err) {
errors.push(sn + ' (PDF: ' + err.message + ')');
}
}
db.close();
console.log('\n\n========================================');
console.log('Results');
console.log('========================================');
console.log(`Generated: ${generated} (TXT + PDF)`);
if (notFound.length > 0) {
console.log(`\nNot in database (${notFound.length}):`);
notFound.forEach(s => console.log(' ' + s));
}
if (noSpecs.length > 0) {
console.log(`\nNo spec data (${noSpecs.length}):`);
noSpecs.forEach(s => console.log(' ' + s));
}
if (errors.length > 0) {
console.log(`\nErrors (${errors.length}):`);
errors.forEach(s => console.log(' ' + s));
}
}
run().catch(console.error);

View File

@@ -0,0 +1,215 @@
/**
* Work Order Report Importer
*
* Imports work order status reports from TS-XX/Reports/ into PostgreSQL.
* Links work order numbers to existing test records.
*
* Usage:
* node import-work-orders.js Full import from all stations
* node import-work-orders.js --file <paths> Import specific report files
* node import-work-orders.js --station TS-4L Import from one station
*/
const fs = require('fs');
const path = require('path');
const db = require('./db');
const { parseWoReport } = require('../parsers/wo-report');
const TEST_PATH = 'C:\\Shares\\test';
async function run() {
const args = process.argv.slice(2);
const stationIdx = args.indexOf('--station');
const targetStation = stationIdx >= 0 ? args[stationIdx + 1] : null;
const fileIdx = args.indexOf('--file');
const specificFiles = fileIdx >= 0 ? args.slice(fileIdx + 1).filter(f => !f.startsWith('--')) : null;
console.log('========================================');
console.log('Work Order Report Import');
console.log('========================================');
console.log(`Start: ${new Date().toISOString()}`);
let files = [];
if (specificFiles && specificFiles.length > 0) {
files = specificFiles;
} else {
try {
const stationDirs = fs.readdirSync(TEST_PATH, { withFileTypes: true })
.filter(d => d.isDirectory() && d.name.match(/^TS-/i))
.filter(d => !targetStation || d.name.toUpperCase() === targetStation.toUpperCase())
.map(d => d.name);
for (const station of stationDirs) {
const reportsDir = path.join(TEST_PATH, station, 'Reports');
if (!fs.existsSync(reportsDir)) continue;
const reportFiles = fs.readdirSync(reportsDir)
.filter(f => f.toUpperCase().endsWith('.TXT'))
.map(f => path.join(reportsDir, f));
files.push(...reportFiles);
}
} catch (err) {
console.error('Error scanning stations:', err.message);
}
}
console.log(`Found ${files.length} report files to import`);
let woCount = 0;
let lineCount = 0;
let linkedCount = 0;
let errors = 0;
const BATCH_SIZE = 500;
let batch = [];
for (const filePath of files) {
try {
const wo = parseWoReport(filePath);
if (!wo.wo_number) continue;
batch.push({ wo, woLines: wo.lines });
if (batch.length >= BATCH_SIZE) {
const result = await processBatch(batch);
woCount += result.woCount;
lineCount += result.lineCount;
linkedCount += result.linkedCount;
batch = [];
process.stdout.write(`\rProcessed: ${woCount} WOs, ${lineCount} lines`);
}
} catch (err) {
errors++;
}
}
// Flush remaining
if (batch.length > 0) {
const result = await processBatch(batch);
woCount += result.woCount;
lineCount += result.lineCount;
linkedCount += result.linkedCount;
}
// Bulk update work_order on test_records from serial number pattern
console.log('\n\nBulk-linking test records by serial number pattern...');
const bulkResult = await db.execute(`
UPDATE test_records
SET work_order = CASE
WHEN serial_number LIKE '%-%'
THEN SPLIT_PART(serial_number, '-', 1)
ELSE serial_number
END
WHERE work_order IS NULL
`);
console.log(`Bulk-linked ${bulkResult.rowCount} test records`);
console.log('\n========================================');
console.log('Import Complete');
console.log('========================================');
console.log(`Work orders imported: ${woCount}`);
console.log(`Test lines imported: ${lineCount}`);
console.log(`Test records linked: ${linkedCount}`);
console.log(`Errors: ${errors}`);
console.log(`End: ${new Date().toISOString()}`);
await db.close();
}
async function processBatch(items) {
let woCount = 0;
let lineCount = 0;
let linkedCount = 0;
await db.transaction(async (txClient) => {
for (const { wo, woLines } of items) {
await txClient.execute(
`INSERT INTO work_orders
(wo_number, wo_date, program, version, lib_version, test_station, source_file)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (wo_number, test_station)
DO UPDATE SET wo_date = EXCLUDED.wo_date, program = EXCLUDED.program,
version = EXCLUDED.version, lib_version = EXCLUDED.lib_version,
source_file = EXCLUDED.source_file`,
[wo.wo_number, wo.wo_date, wo.program, wo.version, wo.lib_version, wo.station, wo.source_file]
);
woCount++;
for (const line of woLines) {
const result = await txClient.execute(
`INSERT INTO work_order_lines
(wo_number, serial_number, status, model_number, ds_filename, test_date, test_time, test_station)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (wo_number, serial_number, test_date, test_time) DO NOTHING`,
[wo.wo_number, line.serial_number, line.status, line.model_number,
line.ds_filename, line.test_date, line.test_time, wo.station]
);
if (result.rowCount > 0) lineCount++;
// Link to test_records
const linked = await txClient.execute(
'UPDATE test_records SET work_order = $1 WHERE serial_number = $2 AND work_order IS NULL',
[wo.wo_number, line.serial_number]
);
if (linked.rowCount > 0) linkedCount++;
}
}
});
return { woCount, lineCount, linkedCount };
}
// Export for use by sync script
async function importReportFiles(filePaths) {
if (!filePaths || filePaths.length === 0) return 0;
let imported = 0;
await db.transaction(async (txClient) => {
for (const filePath of filePaths) {
try {
const wo = parseWoReport(filePath);
if (!wo.wo_number) continue;
await txClient.execute(
`INSERT INTO work_orders
(wo_number, wo_date, program, version, lib_version, test_station, source_file)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (wo_number, test_station)
DO UPDATE SET wo_date = EXCLUDED.wo_date, program = EXCLUDED.program,
version = EXCLUDED.version, lib_version = EXCLUDED.lib_version,
source_file = EXCLUDED.source_file`,
[wo.wo_number, wo.wo_date, wo.program, wo.version, wo.lib_version, wo.station, wo.source_file]
);
for (const line of wo.lines) {
await txClient.execute(
`INSERT INTO work_order_lines
(wo_number, serial_number, status, model_number, ds_filename, test_date, test_time, test_station)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (wo_number, serial_number, test_date, test_time) DO NOTHING`,
[wo.wo_number, line.serial_number, line.status, line.model_number,
line.ds_filename, line.test_date, line.test_time, wo.station]
);
await txClient.execute(
'UPDATE test_records SET work_order = $1 WHERE serial_number = $2 AND work_order IS NULL',
[wo.wo_number, line.serial_number]
);
}
imported++;
} catch (err) {
// skip bad files
}
}
});
console.log(`[WO] Imported ${imported} work order report(s)`);
return imported;
}
if (require.main === module) {
run().catch(console.error);
}
module.exports = { importReportFiles };

View File

@@ -0,0 +1,367 @@
/**
* Data Import Script
* Imports test data from DAT and SHT files into PostgreSQL database
*/
const fs = require('fs');
const path = require('path');
const db = require('./db');
const { parseMultilineFile, extractTestStation } = require('../parsers/multiline');
const { parseCsvFile } = require('../parsers/csvline');
const { parseShtFile } = require('../parsers/shtfile');
// Data source paths
const TEST_PATH = 'C:/Shares/test';
const RECOVERY_PATH = 'C:/Shares/Recovery-TEST';
const HISTLOGS_PATH = path.join(TEST_PATH, 'Ate/HISTLOGS');
// Log types and their parsers
const LOG_TYPES = {
'DSCLOG': { parser: 'multiline', ext: '.DAT' },
'5BLOG': { parser: 'multiline', ext: '.DAT' },
'8BLOG': { parser: 'multiline', ext: '.DAT' },
'PWRLOG': { parser: 'multiline', ext: '.DAT' },
'SCTLOG': { parser: 'multiline', ext: '.DAT' },
'VASLOG': { parser: 'multiline', ext: '.DAT' },
'7BLOG': { parser: 'csvline', ext: '.DAT' }
};
// Find all files of a specific type in a directory
function findFiles(dir, pattern, recursive = true) {
const results = [];
try {
if (!fs.existsSync(dir)) return results;
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const item of items) {
const fullPath = path.join(dir, item.name);
if (item.isDirectory() && recursive) {
results.push(...findFiles(fullPath, pattern, recursive));
} else if (item.isFile()) {
if (pattern.test(item.name)) {
results.push(fullPath);
}
}
}
} catch (err) {
// Ignore permission errors
}
return results;
}
// Parse records from a file (sync -- file I/O only)
function parseFile(filePath, logType, parser) {
const testStation = extractTestStation(filePath);
switch (parser) {
case 'multiline':
return parseMultilineFile(filePath, logType, testStation);
case 'csvline':
return parseCsvFile(filePath, testStation);
case 'shtfile':
return parseShtFile(filePath, testStation);
default:
return [];
}
}
// Batch insert records into PostgreSQL
async function insertBatch(txClient, records) {
let imported = 0;
for (const record of records) {
try {
const result = await txClient.execute(
`INSERT INTO test_records
(log_type, model_number, serial_number, test_date, test_station, overall_result, raw_data, source_file)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (log_type, model_number, serial_number, test_date, test_station)
DO UPDATE SET raw_data = EXCLUDED.raw_data, overall_result = EXCLUDED.overall_result`,
[
record.log_type,
record.model_number,
record.serial_number,
record.test_date,
record.test_station,
record.overall_result,
record.raw_data,
record.source_file
]
);
if (result.rowCount > 0) imported++;
} catch (err) {
// Constraint error - skip
}
}
return imported;
}
// Import records from a file
async function importFile(txClient, filePath, logType, parser) {
let records = [];
try {
records = parseFile(filePath, logType, parser);
const imported = await insertBatch(txClient, records);
return { total: records.length, imported };
} catch (err) {
console.error(`Error importing ${filePath}: ${err.message}`);
return { total: 0, imported: 0 };
}
}
// Import from HISTLOGS (master consolidated logs)
async function importHistlogs(txClient) {
console.log('\n=== Importing from HISTLOGS ===');
let totalImported = 0;
let totalRecords = 0;
for (const [logType, config] of Object.entries(LOG_TYPES)) {
const logDir = path.join(HISTLOGS_PATH, logType);
if (!fs.existsSync(logDir)) {
console.log(` ${logType}: directory not found`);
continue;
}
const files = findFiles(logDir, new RegExp(`\\${config.ext}$`, 'i'), false);
console.log(` ${logType}: found ${files.length} files`);
for (const file of files) {
const { total, imported } = await importFile(txClient, file, logType, config.parser);
totalRecords += total;
totalImported += imported;
}
}
console.log(` HISTLOGS total: ${totalImported} records imported (${totalRecords} parsed)`);
return totalImported;
}
// Import from test station logs
async function importStationLogs(txClient, basePath, label) {
console.log(`\n=== Importing from ${label} ===`);
let totalImported = 0;
let totalRecords = 0;
const stationPattern = /^TS-\d+[LR]?$/i;
let stations = [];
try {
const items = fs.readdirSync(basePath, { withFileTypes: true });
stations = items
.filter(i => i.isDirectory() && stationPattern.test(i.name))
.map(i => i.name);
} catch (err) {
console.log(` Error reading ${basePath}: ${err.message}`);
return 0;
}
console.log(` Found stations: ${stations.join(', ')}`);
for (const station of stations) {
const logsDir = path.join(basePath, station, 'LOGS');
if (!fs.existsSync(logsDir)) continue;
for (const [logType, config] of Object.entries(LOG_TYPES)) {
const logDir = path.join(logsDir, logType);
if (!fs.existsSync(logDir)) continue;
const files = findFiles(logDir, new RegExp(`\\${config.ext}$`, 'i'), false);
for (const file of files) {
const { total, imported } = await importFile(txClient, file, logType, config.parser);
totalRecords += total;
totalImported += imported;
}
}
}
// Also import SHT files
const shtFiles = findFiles(basePath, /\.SHT$/i, true);
console.log(` Found ${shtFiles.length} SHT files`);
for (const file of shtFiles) {
const { total, imported } = await importFile(txClient, file, 'SHT', 'shtfile');
totalRecords += total;
totalImported += imported;
}
console.log(` ${label} total: ${totalImported} records imported (${totalRecords} parsed)`);
return totalImported;
}
// Import from Recovery-TEST backups (newest first)
async function importRecoveryBackups(txClient) {
console.log('\n=== Importing from Recovery-TEST backups ===');
if (!fs.existsSync(RECOVERY_PATH)) {
console.log(' Recovery-TEST directory not found');
return 0;
}
const backups = fs.readdirSync(RECOVERY_PATH, { withFileTypes: true })
.filter(i => i.isDirectory() && /^\d{2}-\d{2}-\d{2}$/.test(i.name))
.map(i => i.name)
.sort()
.reverse();
console.log(` Found backup dates: ${backups.join(', ')}`);
let totalImported = 0;
for (const backup of backups) {
const backupPath = path.join(RECOVERY_PATH, backup);
const imported = await importStationLogs(txClient, backupPath, `Recovery-TEST/${backup}`);
totalImported += imported;
}
return totalImported;
}
// Main import function
async function runImport() {
console.log('========================================');
console.log('Test Data Import');
console.log('========================================');
console.log(`Start time: ${new Date().toISOString()}`);
let grandTotal = 0;
await db.transaction(async (txClient) => {
grandTotal += await importHistlogs(txClient);
grandTotal += await importRecoveryBackups(txClient);
grandTotal += await importStationLogs(txClient, TEST_PATH, 'test');
});
const stats = await db.queryOne('SELECT COUNT(*) as count FROM test_records');
console.log('\n========================================');
console.log('Import Complete');
console.log('========================================');
console.log(`Total records in database: ${stats.count}`);
console.log(`End time: ${new Date().toISOString()}`);
await db.close();
}
// Import a single file (for incremental imports from sync)
async function importSingleFile(filePath) {
console.log(`Importing: ${filePath}`);
let logType = null;
let parser = null;
for (const [type, config] of Object.entries(LOG_TYPES)) {
if (filePath.includes(type)) {
logType = type;
parser = config.parser;
break;
}
}
if (!logType) {
if (/\.SHT$/i.test(filePath)) {
logType = 'SHT';
parser = 'shtfile';
} else {
console.log(` Unknown log type for: ${filePath}`);
return { total: 0, imported: 0 };
}
}
let result;
await db.transaction(async (txClient) => {
result = await importFile(txClient, filePath, logType, parser);
});
console.log(` Imported ${result.imported} of ${result.total} records`);
return result;
}
// Import multiple files (for batch incremental imports)
async function importFiles(filePaths) {
console.log(`\n========================================`);
console.log(`Incremental Import: ${filePaths.length} files`);
console.log(`========================================`);
let totalImported = 0;
let totalRecords = 0;
await db.transaction(async (txClient) => {
for (const filePath of filePaths) {
let logType = null;
let parser = null;
for (const [type, config] of Object.entries(LOG_TYPES)) {
if (filePath.includes(type)) {
logType = type;
parser = config.parser;
break;
}
}
if (!logType) {
if (/\.SHT$/i.test(filePath)) {
logType = 'SHT';
parser = 'shtfile';
} else {
console.log(` Skipping unknown type: ${filePath}`);
continue;
}
}
const { total, imported } = await importFile(txClient, filePath, logType, parser);
totalRecords += total;
totalImported += imported;
console.log(` ${path.basename(filePath)}: ${imported}/${total} records`);
}
});
console.log(`\nTotal: ${totalImported} records imported (${totalRecords} parsed)`);
// Export datasheets for newly imported records
if (totalImported > 0) {
try {
const { loadAllSpecs } = require('../parsers/spec-reader');
const { exportNewRecords } = require('./export-datasheets');
const specMap = loadAllSpecs();
await exportNewRecords(specMap, filePaths);
} catch (err) {
console.error(`[EXPORT] Datasheet export failed: ${err.message}`);
}
}
return { total: totalRecords, imported: totalImported };
}
// Run if called directly
if (require.main === module) {
const args = process.argv.slice(2);
if (args.length > 0 && args[0] === '--file') {
const files = args.slice(1);
if (files.length === 0) {
console.log('Usage: node import.js --file <file1> [file2] ...');
process.exit(1);
}
importFiles(files).then(() => db.close()).catch(console.error);
} else if (args.length > 0 && args[0] === '--help') {
console.log('Usage:');
console.log(' node import.js Full import from all sources');
console.log(' node import.js --file <f> Import specific file(s)');
process.exit(0);
} else {
runImport().catch(console.error);
}
}
module.exports = { runImport, importSingleFile, importFiles };

View File

@@ -0,0 +1,397 @@
/**
* Data Import Script
* Imports test data from DAT and SHT files into SQLite database
*/
const fs = require('fs');
const path = require('path');
const Database = require('better-sqlite3');
const { parseMultilineFile, extractTestStation } = require('../parsers/multiline');
const { parseCsvFile } = require('../parsers/csvline');
const { parseShtFile } = require('../parsers/shtfile');
// Configuration
const DB_PATH = path.join(__dirname, 'testdata.db');
const SCHEMA_PATH = path.join(__dirname, 'schema.sql');
// Data source paths
const TEST_PATH = 'C:/Shares/test';
const RECOVERY_PATH = 'C:/Shares/Recovery-TEST';
const HISTLOGS_PATH = path.join(TEST_PATH, 'Ate/HISTLOGS');
// Log types and their parsers
const LOG_TYPES = {
'DSCLOG': { parser: 'multiline', ext: '.DAT' },
'5BLOG': { parser: 'multiline', ext: '.DAT' },
'8BLOG': { parser: 'multiline', ext: '.DAT' },
'PWRLOG': { parser: 'multiline', ext: '.DAT' },
'SCTLOG': { parser: 'multiline', ext: '.DAT' },
'VASLOG': { parser: 'multiline', ext: '.DAT' },
'7BLOG': { parser: 'csvline', ext: '.DAT' }
};
// Initialize database
function initDatabase() {
console.log('Initializing database...');
const db = new Database(DB_PATH);
// Read and execute schema
const schema = fs.readFileSync(SCHEMA_PATH, 'utf8');
db.exec(schema);
console.log('Database initialized.');
return db;
}
// Prepare insert statement
// Uses INSERT OR REPLACE so re-tested devices keep the latest result
// UNIQUE constraint: (log_type, model_number, serial_number, test_date, test_station)
function prepareInsert(db) {
return db.prepare(`
INSERT OR REPLACE INTO test_records
(log_type, model_number, serial_number, test_date, test_station, overall_result, raw_data, source_file)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`);
}
// Find all files of a specific type in a directory
function findFiles(dir, pattern, recursive = true) {
const results = [];
try {
if (!fs.existsSync(dir)) return results;
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const item of items) {
const fullPath = path.join(dir, item.name);
if (item.isDirectory() && recursive) {
results.push(...findFiles(fullPath, pattern, recursive));
} else if (item.isFile()) {
if (pattern.test(item.name)) {
results.push(fullPath);
}
}
}
} catch (err) {
// Ignore permission errors
}
return results;
}
// Import records from a file
function importFile(db, insertStmt, filePath, logType, parser) {
let records = [];
const testStation = extractTestStation(filePath);
try {
switch (parser) {
case 'multiline':
records = parseMultilineFile(filePath, logType, testStation);
break;
case 'csvline':
records = parseCsvFile(filePath, testStation);
break;
case 'shtfile':
records = parseShtFile(filePath, testStation);
break;
}
let imported = 0;
for (const record of records) {
try {
const result = insertStmt.run(
record.log_type,
record.model_number,
record.serial_number,
record.test_date,
record.test_station,
record.overall_result,
record.raw_data,
record.source_file
);
if (result.changes > 0) imported++;
} catch (err) {
// Duplicate or constraint error - skip
}
}
return { total: records.length, imported };
} catch (err) {
console.error(`Error importing ${filePath}: ${err.message}`);
return { total: 0, imported: 0 };
}
}
// Import from HISTLOGS (master consolidated logs)
function importHistlogs(db, insertStmt) {
console.log('\n=== Importing from HISTLOGS ===');
let totalImported = 0;
let totalRecords = 0;
for (const [logType, config] of Object.entries(LOG_TYPES)) {
const logDir = path.join(HISTLOGS_PATH, logType);
if (!fs.existsSync(logDir)) {
console.log(` ${logType}: directory not found`);
continue;
}
const files = findFiles(logDir, new RegExp(`\\${config.ext}$`, 'i'), false);
console.log(` ${logType}: found ${files.length} files`);
for (const file of files) {
const { total, imported } = importFile(db, insertStmt, file, logType, config.parser);
totalRecords += total;
totalImported += imported;
}
}
console.log(` HISTLOGS total: ${totalImported} records imported (${totalRecords} parsed)`);
return totalImported;
}
// Import from test station logs
function importStationLogs(db, insertStmt, basePath, label) {
console.log(`\n=== Importing from ${label} ===`);
let totalImported = 0;
let totalRecords = 0;
// Find all test station directories (TS-1, TS-27, TS-8L, TS-10R, etc.)
const stationPattern = /^TS-\d+[LR]?$/i;
let stations = [];
try {
const items = fs.readdirSync(basePath, { withFileTypes: true });
stations = items
.filter(i => i.isDirectory() && stationPattern.test(i.name))
.map(i => i.name);
} catch (err) {
console.log(` Error reading ${basePath}: ${err.message}`);
return 0;
}
console.log(` Found stations: ${stations.join(', ')}`);
for (const station of stations) {
const logsDir = path.join(basePath, station, 'LOGS');
if (!fs.existsSync(logsDir)) continue;
for (const [logType, config] of Object.entries(LOG_TYPES)) {
const logDir = path.join(logsDir, logType);
if (!fs.existsSync(logDir)) continue;
const files = findFiles(logDir, new RegExp(`\\${config.ext}$`, 'i'), false);
for (const file of files) {
const { total, imported } = importFile(db, insertStmt, file, logType, config.parser);
totalRecords += total;
totalImported += imported;
}
}
}
// Also import SHT files
const shtFiles = findFiles(basePath, /\.SHT$/i, true);
console.log(` Found ${shtFiles.length} SHT files`);
for (const file of shtFiles) {
const { total, imported } = importFile(db, insertStmt, file, 'SHT', 'shtfile');
totalRecords += total;
totalImported += imported;
}
console.log(` ${label} total: ${totalImported} records imported (${totalRecords} parsed)`);
return totalImported;
}
// Import from Recovery-TEST backups (newest first)
function importRecoveryBackups(db, insertStmt) {
console.log('\n=== Importing from Recovery-TEST backups ===');
if (!fs.existsSync(RECOVERY_PATH)) {
console.log(' Recovery-TEST directory not found');
return 0;
}
// Get backup dates, sort newest first
const backups = fs.readdirSync(RECOVERY_PATH, { withFileTypes: true })
.filter(i => i.isDirectory() && /^\d{2}-\d{2}-\d{2}$/.test(i.name))
.map(i => i.name)
.sort()
.reverse();
console.log(` Found backup dates: ${backups.join(', ')}`);
let totalImported = 0;
for (const backup of backups) {
const backupPath = path.join(RECOVERY_PATH, backup);
const imported = importStationLogs(db, insertStmt, backupPath, `Recovery-TEST/${backup}`);
totalImported += imported;
}
return totalImported;
}
// Main import function
async function runImport() {
console.log('========================================');
console.log('Test Data Import');
console.log('========================================');
console.log(`Database: ${DB_PATH}`);
console.log(`Start time: ${new Date().toISOString()}`);
const db = initDatabase();
const insertStmt = prepareInsert(db);
let grandTotal = 0;
// Use transaction for performance
const importAll = db.transaction(() => {
// 1. Import HISTLOGS first (authoritative)
grandTotal += importHistlogs(db, insertStmt);
// 2. Import Recovery backups (newest first)
grandTotal += importRecoveryBackups(db, insertStmt);
// 3. Import current test folder
grandTotal += importStationLogs(db, insertStmt, TEST_PATH, 'test');
});
importAll();
// Get final stats
const stats = db.prepare('SELECT COUNT(*) as count FROM test_records').get();
console.log('\n========================================');
console.log('Import Complete');
console.log('========================================');
console.log(`Total records in database: ${stats.count}`);
console.log(`End time: ${new Date().toISOString()}`);
db.close();
}
// Import a single file (for incremental imports from sync)
function importSingleFile(filePath) {
console.log(`Importing: ${filePath}`);
const db = new Database(DB_PATH);
const insertStmt = prepareInsert(db);
// Determine log type from path
let logType = null;
let parser = null;
for (const [type, config] of Object.entries(LOG_TYPES)) {
if (filePath.includes(type)) {
logType = type;
parser = config.parser;
break;
}
}
if (!logType) {
// Check for SHT files
if (/\.SHT$/i.test(filePath)) {
logType = 'SHT';
parser = 'shtfile';
} else {
console.log(` Unknown log type for: ${filePath}`);
db.close();
return { total: 0, imported: 0 };
}
}
const result = importFile(db, insertStmt, filePath, logType, parser);
console.log(` Imported ${result.imported} of ${result.total} records`);
db.close();
return result;
}
// Import multiple files (for batch incremental imports)
function importFiles(filePaths) {
console.log(`\n========================================`);
console.log(`Incremental Import: ${filePaths.length} files`);
console.log(`========================================`);
const db = new Database(DB_PATH);
const insertStmt = prepareInsert(db);
let totalImported = 0;
let totalRecords = 0;
const importBatch = db.transaction(() => {
for (const filePath of filePaths) {
// Determine log type from path
let logType = null;
let parser = null;
for (const [type, config] of Object.entries(LOG_TYPES)) {
if (filePath.includes(type)) {
logType = type;
parser = config.parser;
break;
}
}
if (!logType) {
if (/\.SHT$/i.test(filePath)) {
logType = 'SHT';
parser = 'shtfile';
} else {
console.log(` Skipping unknown type: ${filePath}`);
continue;
}
}
const { total, imported } = importFile(db, insertStmt, filePath, logType, parser);
totalRecords += total;
totalImported += imported;
console.log(` ${path.basename(filePath)}: ${imported}/${total} records`);
}
});
importBatch();
console.log(`\nTotal: ${totalImported} records imported (${totalRecords} parsed)`);
db.close();
return { total: totalRecords, imported: totalImported };
}
// Run if called directly
if (require.main === module) {
// Check for command line arguments
const args = process.argv.slice(2);
if (args.length > 0 && args[0] === '--file') {
// Import specific file(s)
const files = args.slice(1);
if (files.length === 0) {
console.log('Usage: node import.js --file <file1> [file2] ...');
process.exit(1);
}
importFiles(files);
} else if (args.length > 0 && args[0] === '--help') {
console.log('Usage:');
console.log(' node import.js Full import from all sources');
console.log(' node import.js --file <f> Import specific file(s)');
process.exit(0);
} else {
// Full import
runImport().catch(console.error);
}
}
module.exports = { runImport, importSingleFile, importFiles };

View File

@@ -0,0 +1,260 @@
/**
* SQLite to PostgreSQL Data Migration
*
* Streams all data from the SQLite testdata.db into PostgreSQL.
* Uses batch INSERTs for performance.
*
* Usage:
* node migrate-data.js Migrate all tables
* node migrate-data.js --skip-tsvector Skip tsvector rebuild (faster, trigger handles it)
* node migrate-data.js --table test_records Migrate only one table
*/
const path = require('path');
const Database = require('better-sqlite3');
const db = require('./db');
const SQLITE_PATH = path.join(__dirname, 'testdata.db');
const BATCH_SIZE = 5000;
async function migrateTestRecords(sqlite) {
console.log('\n--- Migrating test_records ---');
const total = sqlite.prepare('SELECT COUNT(*) as cnt FROM test_records').get().cnt;
console.log(` Source records: ${total.toLocaleString()}`);
// Disable triggers during bulk load for performance
await db.execute('ALTER TABLE test_records DISABLE TRIGGER trg_search_vector');
const stmt = sqlite.prepare('SELECT * FROM test_records ORDER BY id');
let migrated = 0;
let batch = [];
for (const row of stmt.iterate()) {
batch.push(row);
if (batch.length >= BATCH_SIZE) {
await insertTestRecordsBatch(batch);
migrated += batch.length;
batch = [];
process.stdout.write(`\r Migrated: ${migrated.toLocaleString()} / ${total.toLocaleString()}`);
}
}
// Flush remaining
if (batch.length > 0) {
await insertTestRecordsBatch(batch);
migrated += batch.length;
}
console.log(`\r Migrated: ${migrated.toLocaleString()} / ${total.toLocaleString()}`);
// Rebuild search_vector for all rows
console.log(' Rebuilding search_vector (this may take a few minutes)...');
await db.execute(`
UPDATE test_records SET search_vector = to_tsvector('english',
COALESCE(serial_number, '') || ' ' ||
COALESCE(model_number, '') || ' ' ||
COALESCE(raw_data, '')
)
`);
console.log(' search_vector rebuilt.');
// Re-enable trigger
await db.execute('ALTER TABLE test_records ENABLE TRIGGER trg_search_vector');
// Reset sequence to max id
await db.execute(`SELECT setval('test_records_id_seq', (SELECT COALESCE(MAX(id), 1) FROM test_records))`);
return migrated;
}
async function insertTestRecordsBatch(batch) {
// Build a multi-row INSERT
const cols = ['id', 'log_type', 'model_number', 'serial_number', 'test_date',
'test_station', 'overall_result', 'raw_data', 'source_file',
'import_date', 'datasheet_exported_at', 'forweb_exported_at', 'work_order'];
const values = [];
const params = [];
let paramIdx = 0;
for (const row of batch) {
const placeholders = cols.map(() => {
paramIdx++;
return `$${paramIdx}`;
});
values.push(`(${placeholders.join(',')})`);
params.push(
row.id,
row.log_type,
row.model_number,
row.serial_number,
row.test_date,
row.test_station,
row.overall_result,
row.raw_data,
row.source_file,
row.import_date,
row.datasheet_exported_at,
row.forweb_exported_at,
row.work_order
);
}
const sql = `INSERT INTO test_records (${cols.join(',')})
VALUES ${values.join(',')}
ON CONFLICT (log_type, model_number, serial_number, test_date, test_station)
DO NOTHING`;
await db.execute(sql, params);
}
async function migrateWorkOrders(sqlite) {
console.log('\n--- Migrating work_orders ---');
const rows = sqlite.prepare('SELECT * FROM work_orders ORDER BY id').all();
console.log(` Source records: ${rows.length.toLocaleString()}`);
let migrated = 0;
const cols = ['wo_number', 'wo_date', 'program', 'version',
'lib_version', 'test_station', 'source_file', 'import_date'];
for (let i = 0; i < rows.length; i += BATCH_SIZE) {
const batch = rows.slice(i, i + BATCH_SIZE);
const values = [];
const params = [];
let paramIdx = 0;
for (const row of batch) {
const placeholders = cols.map(() => { paramIdx++; return `$${paramIdx}`; });
values.push(`(${placeholders.join(',')})`);
params.push(row.wo_number, row.wo_date, row.program, row.version,
row.lib_version, row.test_station, row.source_file, row.import_date);
}
await db.execute(
`INSERT INTO work_orders (${cols.join(',')}) VALUES ${values.join(',')}
ON CONFLICT (wo_number, test_station) DO NOTHING`,
params
);
migrated += batch.length;
}
console.log(` Migrated: ${migrated.toLocaleString()}`);
return migrated;
}
async function migrateWorkOrderLines(sqlite) {
console.log('\n--- Migrating work_order_lines ---');
const total = sqlite.prepare('SELECT COUNT(*) as cnt FROM work_order_lines').get().cnt;
console.log(` Source records: ${total.toLocaleString()}`);
const stmt = sqlite.prepare('SELECT * FROM work_order_lines ORDER BY id');
let migrated = 0;
let batch = [];
for (const row of stmt.iterate()) {
batch.push(row);
if (batch.length >= BATCH_SIZE) {
await insertWoLinesBatch(batch);
migrated += batch.length;
batch = [];
process.stdout.write(`\r Migrated: ${migrated.toLocaleString()} / ${total.toLocaleString()}`);
}
}
if (batch.length > 0) {
await insertWoLinesBatch(batch);
migrated += batch.length;
}
console.log(`\r Migrated: ${migrated.toLocaleString()} / ${total.toLocaleString()}`);
return migrated;
}
async function insertWoLinesBatch(batch) {
const cols = ['wo_number', 'serial_number', 'status', 'model_number',
'ds_filename', 'test_date', 'test_time', 'test_station'];
const values = [];
const params = [];
let paramIdx = 0;
for (const row of batch) {
const placeholders = cols.map(() => { paramIdx++; return `$${paramIdx}`; });
values.push(`(${placeholders.join(',')})`);
params.push(row.wo_number, row.serial_number, row.status,
row.model_number, row.ds_filename, row.test_date, row.test_time, row.test_station);
}
await db.execute(
`INSERT INTO work_order_lines (${cols.join(',')}) VALUES ${values.join(',')}
ON CONFLICT (wo_number, serial_number, test_date, test_time) DO NOTHING`,
params
);
}
async function main() {
const args = process.argv.slice(2);
const tableArg = args.indexOf('--table');
const targetTable = tableArg >= 0 ? args[tableArg + 1] : null;
console.log('========================================');
console.log('SQLite -> PostgreSQL Data Migration');
console.log('========================================');
console.log(`SQLite: ${SQLITE_PATH}`);
console.log(`Start: ${new Date().toISOString()}`);
const sqlite = new Database(SQLITE_PATH, { readonly: true });
try {
if (!targetTable || targetTable === 'test_records') {
await migrateTestRecords(sqlite);
}
if (!targetTable || targetTable === 'work_orders') {
await migrateWorkOrders(sqlite);
}
if (!targetTable || targetTable === 'work_order_lines') {
await migrateWorkOrderLines(sqlite);
}
// VACUUM ANALYZE
console.log('\n--- Running VACUUM ANALYZE ---');
await db.execute('VACUUM ANALYZE test_records');
await db.execute('VACUUM ANALYZE work_orders');
await db.execute('VACUUM ANALYZE work_order_lines');
console.log(' Done.');
// Verify counts
console.log('\n--- Verification ---');
const pgTestCount = await db.queryOne('SELECT COUNT(*) as cnt FROM test_records');
const pgWoCount = await db.queryOne('SELECT COUNT(*) as cnt FROM work_orders');
const pgWolCount = await db.queryOne('SELECT COUNT(*) as cnt FROM work_order_lines');
const sqliteTestCount = sqlite.prepare('SELECT COUNT(*) as cnt FROM test_records').get().cnt;
const sqliteWoCount = sqlite.prepare('SELECT COUNT(*) as cnt FROM work_orders').get().cnt;
const sqliteWolCount = sqlite.prepare('SELECT COUNT(*) as cnt FROM work_order_lines').get().cnt;
console.log(` test_records: SQLite=${sqliteTestCount.toLocaleString()} PG=${parseInt(pgTestCount.cnt).toLocaleString()} ${parseInt(pgTestCount.cnt) === sqliteTestCount ? '[OK]' : '[MISMATCH]'}`);
console.log(` work_orders: SQLite=${sqliteWoCount.toLocaleString()} PG=${parseInt(pgWoCount.cnt).toLocaleString()} ${parseInt(pgWoCount.cnt) === sqliteWoCount ? '[OK]' : '[MISMATCH]'}`);
console.log(` work_order_lines: SQLite=${sqliteWolCount.toLocaleString()} PG=${parseInt(pgWolCount.cnt).toLocaleString()} ${parseInt(pgWolCount.cnt) === sqliteWolCount ? '[OK]' : '[MISMATCH]'}`);
} finally {
sqlite.close();
await db.close();
}
console.log(`\n========================================`);
console.log(`Migration Complete`);
console.log(`========================================`);
console.log(`End: ${new Date().toISOString()}`);
}
main().catch(err => {
console.error('Migration failed:', err);
process.exit(1);
});

View File

@@ -0,0 +1,96 @@
-- TestDataDB PostgreSQL Schema
-- Migrated from SQLite schema.sql
-- PostgreSQL 18 on AD2 (192.168.0.6)
-- Main test records table
CREATE TABLE IF NOT EXISTS test_records (
id BIGSERIAL PRIMARY KEY,
log_type TEXT NOT NULL,
model_number TEXT NOT NULL,
serial_number TEXT NOT NULL,
test_date TEXT NOT NULL,
test_station TEXT,
overall_result TEXT,
raw_data TEXT,
source_file TEXT,
import_date TIMESTAMPTZ DEFAULT NOW(),
datasheet_exported_at TIMESTAMPTZ DEFAULT NULL,
forweb_exported_at TIMESTAMPTZ DEFAULT NULL,
work_order TEXT DEFAULT NULL,
search_vector tsvector,
UNIQUE(log_type, model_number, serial_number, test_date, test_station)
);
-- Indexes for fast searching
CREATE INDEX IF NOT EXISTS idx_serial ON test_records(serial_number);
CREATE INDEX IF NOT EXISTS idx_model ON test_records(model_number);
CREATE INDEX IF NOT EXISTS idx_date ON test_records(test_date);
CREATE INDEX IF NOT EXISTS idx_model_serial ON test_records(model_number, serial_number);
CREATE INDEX IF NOT EXISTS idx_result ON test_records(overall_result);
CREATE INDEX IF NOT EXISTS idx_log_type ON test_records(log_type);
CREATE INDEX IF NOT EXISTS idx_test_wo ON test_records(work_order);
-- Partial index for unexported PASS records (speeds up export queries)
CREATE INDEX IF NOT EXISTS idx_unexported_pass ON test_records(overall_result, forweb_exported_at)
WHERE overall_result = 'PASS' AND forweb_exported_at IS NULL;
-- GIN index for full-text search (replaces SQLite FTS5 virtual table)
CREATE INDEX IF NOT EXISTS idx_search_vector ON test_records USING GIN(search_vector);
-- Trigger function to maintain search_vector on INSERT/UPDATE
CREATE OR REPLACE FUNCTION update_search_vector() RETURNS trigger AS $$
BEGIN
NEW.search_vector := to_tsvector('english',
COALESCE(NEW.serial_number, '') || ' ' ||
COALESCE(NEW.model_number, '') || ' ' ||
COALESCE(NEW.raw_data, '')
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Drop trigger if exists, then create
DROP TRIGGER IF EXISTS trg_search_vector ON test_records;
CREATE TRIGGER trg_search_vector
BEFORE INSERT OR UPDATE ON test_records
FOR EACH ROW
EXECUTE FUNCTION update_search_vector();
-- Work orders table
CREATE TABLE IF NOT EXISTS work_orders (
id BIGSERIAL PRIMARY KEY,
wo_number TEXT NOT NULL,
wo_date TEXT,
program TEXT,
version TEXT,
lib_version TEXT,
test_station TEXT,
source_file TEXT,
import_date TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(wo_number, test_station)
);
CREATE INDEX IF NOT EXISTS idx_wo_number ON work_orders(wo_number);
CREATE INDEX IF NOT EXISTS idx_wo_station ON work_orders(test_station);
-- Work order lines table
CREATE TABLE IF NOT EXISTS work_order_lines (
id BIGSERIAL PRIMARY KEY,
wo_number TEXT NOT NULL,
serial_number TEXT NOT NULL,
status TEXT,
model_number TEXT,
ds_filename TEXT,
test_date TEXT,
test_time TEXT,
test_station TEXT,
UNIQUE(wo_number, serial_number, test_date, test_time)
);
CREATE INDEX IF NOT EXISTS idx_wol_wo ON work_order_lines(wo_number);
CREATE INDEX IF NOT EXISTS idx_wol_serial ON work_order_lines(serial_number);
CREATE INDEX IF NOT EXISTS idx_wol_model ON work_order_lines(model_number);
-- Grant permissions to app role
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO testdatadb_app;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO testdatadb_app;

View File

@@ -0,0 +1,54 @@
-- Test Data Database Schema
-- SQLite database for storing and searching test records
-- Main test records table
CREATE TABLE IF NOT EXISTS test_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
log_type TEXT NOT NULL, -- DSCLOG, 5BLOG, 7BLOG, 8BLOG, PWRLOG, SCTLOG, VASLOG, SHT
model_number TEXT NOT NULL, -- DSCA38-1793, SCM5B30-01, etc.
serial_number TEXT NOT NULL, -- 176923-1, 105840-2, etc.
test_date TEXT NOT NULL, -- Test date (YYYY-MM-DD format)
test_station TEXT, -- TS-1L, TS-3R, etc.
overall_result TEXT, -- PASS/FAIL
raw_data TEXT, -- Full original record
source_file TEXT, -- Original file path
import_date TEXT DEFAULT (datetime('now')),
datasheet_exported_at TEXT DEFAULT NULL,
forweb_exported_at TEXT DEFAULT NULL,
UNIQUE(log_type, model_number, serial_number, test_date, test_station)
);
-- Indexes for fast searching
CREATE INDEX IF NOT EXISTS idx_serial ON test_records(serial_number);
CREATE INDEX IF NOT EXISTS idx_model ON test_records(model_number);
CREATE INDEX IF NOT EXISTS idx_date ON test_records(test_date);
CREATE INDEX IF NOT EXISTS idx_model_serial ON test_records(model_number, serial_number);
CREATE INDEX IF NOT EXISTS idx_result ON test_records(overall_result);
CREATE INDEX IF NOT EXISTS idx_log_type ON test_records(log_type);
-- Full-text search virtual table
CREATE VIRTUAL TABLE IF NOT EXISTS test_records_fts USING fts5(
serial_number,
model_number,
raw_data,
content='test_records',
content_rowid='id'
);
-- Triggers to keep FTS index in sync
CREATE TRIGGER IF NOT EXISTS test_records_ai AFTER INSERT ON test_records BEGIN
INSERT INTO test_records_fts(rowid, serial_number, model_number, raw_data)
VALUES (new.id, new.serial_number, new.model_number, new.raw_data);
END;
CREATE TRIGGER IF NOT EXISTS test_records_ad AFTER DELETE ON test_records BEGIN
INSERT INTO test_records_fts(test_records_fts, rowid, serial_number, model_number, raw_data)
VALUES ('delete', old.id, old.serial_number, old.model_number, old.raw_data);
END;
CREATE TRIGGER IF NOT EXISTS test_records_au AFTER UPDATE ON test_records BEGIN
INSERT INTO test_records_fts(test_records_fts, rowid, serial_number, model_number, raw_data)
VALUES ('delete', old.id, old.serial_number, old.model_number, old.raw_data);
INSERT INTO test_records_fts(rowid, serial_number, model_number, raw_data)
VALUES (new.id, new.serial_number, new.model_number, new.raw_data);
END;

View File

@@ -0,0 +1,72 @@
/**
* Parser for single-line CSV format (7BLOG)
*
* Format:
* STAGE: MODEL,SERIAL,DATE,VERSION,CODE,VALUE1,VALUE2,...
* Example:
* FINAL: 7B21,87876-1,05-08-2013,1.984,0651945, 12, 9999, ...
*/
const fs = require('fs');
const path = require('path');
/**
* Parse a 7BLOG CSV file and extract test records
* @param {string} filePath - Path to the DAT file
* @param {string} testStation - Test station identifier
* @returns {Array} Array of parsed records
*/
function parseCsvFile(filePath, testStation = null) {
const records = [];
try {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n').map(l => l.trim());
for (const line of lines) {
if (!line) continue;
// Match pattern: STAGE: MODEL,SERIAL,DATE,...
const match = line.match(/^([A-Z-]+):\s*([^,]+),([^,]+),(\d{2}-\d{2}-\d{4}),(.*)$/);
if (match) {
const [, stage, model, serial, dateStr, rest] = match;
// Parse date from MM-DD-YYYY to YYYY-MM-DD
const [month, day, year] = dateStr.split('-');
const testDate = `${year}-${month}-${day}`;
// Model number includes the stage prefix for 7B products
const modelNumber = model.trim();
records.push({
log_type: '7BLOG',
model_number: modelNumber,
serial_number: serial.trim(),
test_date: testDate,
test_station: testStation,
overall_result: 'PASS', // 7BLOG entries are typically passing records
raw_data: line,
source_file: filePath
});
}
}
} catch (err) {
console.error(`Error parsing ${filePath}: ${err.message}`);
}
return records;
}
/**
* Extract test station from file path
*/
function extractTestStation(filePath) {
const match = filePath.match(/TS-\d+[LR]/i);
return match ? match[0].toUpperCase() : null;
}
module.exports = {
parseCsvFile,
extractTestStation
};

View File

@@ -0,0 +1,119 @@
/**
* Parser for multi-line DAT files (DSCLOG, 5BLOG, 8BLOG, PWRLOG, SCTLOG, VASLOG)
*
* Format:
* "MODEL_NUMBER "
* measurement1,measurement2,measurement3,measurement4,"PASS/FAIL"
* ... (test data lines)
* 0
* "summary line 1"
* ...
* "SERIAL-NUM","MM-DD-YYYY"
*/
const fs = require('fs');
const path = require('path');
/**
* Parse a multi-line DAT file and extract test records
* @param {string} filePath - Path to the DAT file
* @param {string} logType - Type of log (DSCLOG, 5BLOG, etc.)
* @param {string} testStation - Test station identifier (TS-1L, etc.)
* @returns {Array} Array of parsed records
*/
function parseMultilineFile(filePath, logType, testStation = null) {
const records = [];
try {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n').map(l => l.trim());
let currentRecord = [];
let modelNumber = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Skip empty lines
if (!line) continue;
// Check if it's a serial/date line (format: "SERIAL","DATE")
const serialDateMatch = line.match(/^"(\d+-\d+[A-Za-z]?)","(\d{2}-\d{2}-\d{4})"$/);
if (serialDateMatch) {
// This is the end of a record
const serialNumber = serialDateMatch[1];
const dateStr = serialDateMatch[2];
if (modelNumber && currentRecord.length > 0) {
// Parse date from MM-DD-YYYY to YYYY-MM-DD
const [month, day, year] = dateStr.split('-');
const testDate = `${year}-${month}-${day}`;
// Determine overall result from raw data
const rawData = currentRecord.join('\n');
const overallResult = determineResult(rawData);
records.push({
log_type: logType,
model_number: modelNumber.trim(),
serial_number: serialNumber,
test_date: testDate,
test_station: testStation,
overall_result: overallResult,
raw_data: rawData,
source_file: filePath
});
}
// Reset for next record
currentRecord = [];
modelNumber = null;
}
// Check if this is a model number line
// Model numbers: single quoted string with product code (letters+numbers, possibly with dash)
// Examples: "DSCA38-1793 ", "SCM5B30-01 ", "8B30-01 "
else if (/^"[A-Z0-9]+[A-Z0-9-]*\s*"$/.test(line) && !line.includes(',') && !line.includes('PASS') && !line.includes('FAIL')) {
// This is a model number line - start new record
if (currentRecord.length > 0 && modelNumber) {
// Previous record didn't have serial/date - skip it
currentRecord = [];
}
modelNumber = line.replace(/"/g, '').trim();
currentRecord.push(line);
} else {
// Add line to current record
currentRecord.push(line);
}
}
} catch (err) {
console.error(`Error parsing ${filePath}: ${err.message}`);
}
return records;
}
/**
* Determine overall PASS/FAIL result from raw data
*/
function determineResult(rawData) {
const failCount = (rawData.match(/"FAIL/gi) || []).length;
const passCount = (rawData.match(/"PASS/gi) || []).length;
if (failCount > 0) return 'FAIL';
if (passCount > 0) return 'PASS';
return 'UNKNOWN';
}
/**
* Extract test station from file path
*/
function extractTestStation(filePath) {
const match = filePath.match(/TS-\d+[LR]/i);
return match ? match[0].toUpperCase() : null;
}
module.exports = {
parseMultilineFile,
extractTestStation
};

View File

@@ -0,0 +1,96 @@
/**
* Parser for SHT (Test Data Sheet) files
*
* Format:
* DATAFORTH CORPORATION ...
* ...
* TEST DATA SHEET
* ~~~~~~~~~~~~~~~~~~~~~~~
* Date: MM-DD-YYYY
* Model: MODEL_NUMBER
* SN: SERIAL-NUM
*
* Parameter Measured Value Specification Status
* ======================= =============== ==================== ======
* Supply Current 12.0 mA < 30 mA PASS
* ...
*/
const fs = require('fs');
const path = require('path');
/**
* Parse an SHT file and extract the test record
* @param {string} filePath - Path to the SHT file
* @param {string} testStation - Test station identifier
* @returns {Array} Array with single parsed record (or empty if parse fails)
*/
function parseShtFile(filePath, testStation = null) {
const records = [];
try {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
let date = null;
let model = null;
let serial = null;
let hasFailure = false;
for (const line of lines) {
// Extract date
const dateMatch = line.match(/^Date:\s*(\d{2}-\d{2}-\d{4})/);
if (dateMatch) {
const [month, day, year] = dateMatch[1].split('-');
date = `${year}-${month}-${day}`;
}
// Extract model
const modelMatch = line.match(/^Model:\s*(\S+)/);
if (modelMatch) {
model = modelMatch[1].trim();
}
// Extract serial number
const snMatch = line.match(/^SN:\s*(\S+)/);
if (snMatch) {
serial = snMatch[1].trim();
}
// Check for FAIL status
if (/\bFAIL\b/i.test(line)) {
hasFailure = true;
}
}
if (date && model && serial) {
records.push({
log_type: 'SHT',
model_number: model,
serial_number: serial,
test_date: date,
test_station: testStation,
overall_result: hasFailure ? 'FAIL' : 'PASS',
raw_data: content,
source_file: filePath
});
}
} catch (err) {
console.error(`Error parsing ${filePath}: ${err.message}`);
}
return records;
}
/**
* Extract test station from file path
*/
function extractTestStation(filePath) {
const match = filePath.match(/TS-\d+[LR]/i);
return match ? match[0].toUpperCase() : null;
}
module.exports = {
parseShtFile,
extractTestStation
};

View File

@@ -0,0 +1,487 @@
/**
* Spec Reader - Parses QuickBASIC binary DAT spec files
*
* Reads model specification data from 4 product family DAT files:
* 5BMAIN.DAT (SCM5B family, 160 bytes/record)
* 8BMAIN.DAT (8B family, 163 bytes/record)
* DSCOUT.DAT (DSCA family, 163 bytes/record)
* SCTMAIN.DAT (DSCT family, 121 bytes/record)
*
* These are QuickBASIC random-access files using TYPE (struct) records.
* All values are little-endian: SINGLE = IEEE 754 float (4 bytes),
* INTEGER = signed 16-bit (2 bytes), STRING * N = fixed-width ASCII.
*/
const fs = require('fs');
const path = require('path');
// Default spec data directory
const DEFAULT_SPEC_DIR = path.join(__dirname, '..', 'specdata');
// --------------------------------------------------------------------------
// Binary read helpers
// --------------------------------------------------------------------------
function readString(buf, offset, length) {
return buf.toString('ascii', offset, offset + length).replace(/\0/g, '').trim();
}
function readSingle(buf, offset) {
return buf.readFloatLE(offset);
}
function readInteger(buf, offset) {
return buf.readInt16LE(offset);
}
// --------------------------------------------------------------------------
// TYPE definitions (field name, type, size)
// --------------------------------------------------------------------------
const FIELD_TYPES = {
STRING17: { size: 17, read: (buf, off) => readString(buf, off, 17) },
STRING9: { size: 9, read: (buf, off) => readString(buf, off, 9) },
STRING15: { size: 15, read: (buf, off) => readString(buf, off, 15) },
STRING14: { size: 14, read: (buf, off) => readString(buf, off, 14) },
STRING13: { size: 13, read: (buf, off) => readString(buf, off, 13) },
STRING7: { size: 7, read: (buf, off) => readString(buf, off, 7) },
SINGLE: { size: 4, read: (buf, off) => readSingle(buf, off) },
INTEGER: { size: 2, read: (buf, off) => readInteger(buf, off) },
};
const S15 = 'STRING15';
const S14 = 'STRING14';
const S13 = 'STRING13';
const S7 = 'STRING7';
const SNG = 'SINGLE';
const INT = 'INTEGER';
// SCM5B: 160 bytes/record
const SCM5B_FIELDS = [
['MODNAME', S15], ['SENTYPE', S7],
['ISMAXNEXCL', SNG], ['ISMAXFEXCL', SNG], ['OUTRES', SNG],
['MININ', SNG], ['MAXIN', SNG], ['IEXC', SNG],
['RCONV', SNG], ['MINOUT', SNG], ['MAXOUT', SNG],
['OSCALPT', SNG], ['GNCALPT', SNG], ['CALTOL', SNG],
['VEXC', SNG], ['VEXCACC', SNG], ['EXCLOAD', SNG],
['EXCLOADREG', SNG], ['EXCIMAX', SNG], ['LINEAR', SNG],
['ACCURACY', SNG], ['TESTFREQ', SNG], ['ATTEN', SNG],
['ATTENTOL', SNG], ['STEPRMIN', SNG], ['STEPRMAX', SNG],
['PSS', SNG], ['OUTNOISE', SNG], ['INPUTRES', SNG],
['VOPENINMIN', SNG], ['VOPENINMAX', SNG], ['LEADRERR', SNG],
['LINEARIZED', INT], ['OSCALIN', SNG], ['GNCALIN', SNG],
['BANDWIDTH', SNG], ['IMATCHTOL', SNG],
];
// 8B: 163 bytes/record (no OUTRES, has OUTSIGTYPE)
const B8_FIELDS = [
['MODNAME', S15], ['SENTYPE', S7],
['ISMAXNEXCL', SNG], ['ISMAXFEXCL', SNG],
['MININ', SNG], ['MAXIN', SNG], ['IEXC', SNG],
['RCONV', SNG], ['OUTSIGTYPE', S7],
['MINOUT', SNG], ['MAXOUT', SNG],
['OSCALPT', SNG], ['GNCALPT', SNG], ['CALTOL', SNG],
['VEXC', SNG], ['VEXCACC', SNG], ['EXCLOAD', SNG],
['EXCLOADREG', SNG], ['EXCIMAX', SNG], ['LINEAR', SNG],
['ACCURACY', SNG], ['TESTFREQ', SNG], ['ATTEN', SNG],
['ATTENTOL', SNG], ['STEPRMIN', SNG], ['STEPRMAX', SNG],
['PSS', SNG], ['OUTNOISE', SNG], ['INPUTRES', SNG],
['VOPENINMIN', SNG], ['VOPENINMAX', SNG], ['LEADRERR', SNG],
['LINEARIZED', INT], ['OSCALIN', SNG], ['GNCALIN', SNG],
['BANDWIDTH', SNG], ['IMATCHTOL', SNG],
];
// DSCA: 163 bytes/record
const DSCA_FIELDS = [
['MODNAME', S13], ['SENTYPE', S7],
['ISMAXNL', SNG], ['ISMAXFL', SNG],
['MININ', SNG], ['MAXIN', SNG], ['RCONV', SNG],
['MINOUT', SNG], ['MAXOUT', SNG], ['OUTSIGTYPE', S7],
['OSCALIN', SNG], ['GNCALIN', SNG], ['CALTOL', SNG],
['LOAD1', SNG], ['LINEAR1', SNG], ['ACCURACY1', SNG],
['LOAD2', SNG], ['LINEAR2', SNG], ['ACCURACY2', SNG],
['LOAD3', SNG], ['LINEAR3', SNG], ['ACCURACY3', SNG],
['BANDWIDTH', SNG], ['TESTFREQ', SNG], ['ATTEN', SNG],
['ATTENTOL', SNG], ['STEPRMIN', SNG], ['STEPRMAX', SNG],
['PSS', SNG], ['OUTNOISE', SNG], ['INPUTRES', SNG],
['COMPLIANCE', SNG], ['MAXLOAD', SNG], ['ILIMIT', SNG],
['PERCOVER', SNG], ['MINVS', SNG], ['MAXVS', SNG],
];
// DSCT: 121 bytes/record (uses INTEGER for some fields)
const DSCT_FIELDS = [
['MODNAME', S14], ['SENTYPE', S7],
['MININ', SNG], ['MAXIN', SNG],
['IEXCMFS', SNG], ['IEXCPFS', SNG],
['RCONV', SNG], ['MINOUT', SNG], ['MAXOUT', SNG],
['OSCALPT', SNG], ['GNCALPT', SNG],
['LINEAR', SNG], ['ACCURACY', SNG],
['TESTFREQ', INT], ['ATTEN', INT], ['ATTENTOL', INT],
['STEPRMIN', SNG], ['STEPRMAX', SNG],
['OUTNOISE', SNG], ['INPUTRES', SNG],
['IOPENTC', SNG], ['LEADRERR', SNG],
['LINEARIZED', INT], ['OSCALIN', SNG], ['GNCALIN', SNG],
['BANDWIDTH', SNG], ['IMATCHTOL', SNG],
['CALTOL', SNG], ['VSEN', SNG],
];
const S9 = 'STRING9';
// SCM5B45: 119 bytes/record (frequency/counter modules)
const SCM5B45_FIELDS = [
['MODNAME', S9],
['MININ', SNG], ['MAXIN', SNG], ['MINOUT', SNG], ['MAXOUT', SNG],
['ZHYSAMPL', SNG], ['ZHYSLIM', SNG], ['TTLHYSAMPL', SNG],
['TTLLIMHI', SNG], ['TTLLIMLO', SNG], ['MINPW', SNG],
['OSCALIN', SNG], ['GNCALIN', SNG], ['CALTOL', SNG],
['LINEAR', SNG], ['ACCURACY', SNG],
['TESTFREQ', INT], ['ATTEN', INT], ['ATTENTOL', INT],
['STEPRMIN', SNG], ['STEPRMAX', SNG],
['ISMAX', SNG], ['PSS', SNG],
['NOISEMFS', SNG], ['NOISETESTPT', SNG], ['NOISEPFS', SNG],
['OUTRES', SNG], ['EXCVOLT', SNG],
['EXCTOLNL', SNG], ['EXCTOLL', SNG],
];
// SCM5B48: 264 bytes/record (multi-bandwidth modules)
const SCM5B48_FIELDS = [
['MODNAME', S15], ['SENTYPE', S7],
['ISMAXNEXCL', SNG], ['ISMAXFEXCL', SNG], ['OUTRES', SNG],
['MININ', SNG], ['MAXIN', SNG],
['MININ1', SNG], ['MAXIN1', SNG],
['MININ2', SNG], ['MAXIN2', SNG],
['MININ3', SNG], ['MAXIN3', SNG],
['IEXC', SNG], ['IEXC1', SNG], ['IEXC2', SNG],
['RCONV', SNG], ['MINOUT', SNG], ['MAXOUT', SNG],
['OSCALPT', SNG], ['GNCALPT', SNG], ['CALTOL', SNG],
['VEXC', SNG], ['VEXCACC', SNG], ['EXCLOAD', SNG],
['EXCLOADREG', SNG], ['EXCIMAX', SNG],
['LINEAR', SNG], ['ACCURACY', SNG],
['TESTFREQ', SNG], ['TESTFREQ1', SNG], ['TESTFREQ2', SNG], ['TESTFREQ3', SNG], ['TESTFREQ4', SNG],
['ATTEN', SNG], ['ATTEN1', SNG], ['ATTEN2', SNG], ['ATTEN3', SNG], ['ATTEN4', SNG],
['ATTENTOL', SNG],
['STEPRMIN', SNG], ['STEPRMAX', SNG],
['PSS', SNG], ['PSS1', SNG], ['PSS2', SNG], ['PSS3', SNG],
['OUTNOISE', SNG], ['OUTNOISE1', SNG], ['OUTNOISE2', SNG], ['OUTNOISE3', SNG],
['INPUTRES', SNG], ['VOPENINMIN', SNG], ['VOPENINMAX', SNG], ['LEADRERR', SNG],
['LINEARIZED', INT],
['OSCALIN', SNG], ['GNCALIN', SNG],
['BANDWIDTH', SNG], ['BANDWIDTH1', SNG], ['BANDWIDTH2', SNG], ['BANDWIDTH3', SNG], ['BANDWIDTH4', SNG],
['IMATCHTOL', SNG],
];
// SCM5B49: 93 bytes/record (sample & hold modules)
const SCM5B49_FIELDS = [
['MODNAME', S9],
['MININ', SNG], ['MAXIN', SNG], ['MINOUT', SNG], ['MAXOUT', SNG],
['MAXSUPPLYNL', SNG], ['MAXSUPPLYFL', SNG], ['LIMITOUT', SNG], ['POWERSEN', SNG],
['TESTFREQ', INT], ['ATTEN', INT],
['LINEAR0MA', SNG], ['LINEAR50MA', SNG],
['ACCURACY0MA', SNG], ['ACCURACY50MA', SNG],
['STEPRMIN', SNG], ['STEPRMAX', SNG],
['NOISEOUT', SNG], ['QINJECT', SNG],
['INPUTRES', SNG], ['ACQLIM', SNG],
['DROOP', SNG], ['PERCOVER', SNG],
];
// DSCA (TSTDIN1B variant, for DSCMAIN4.DAT): 159 bytes/record
const DSCA_DIN_FIELDS = [
['MODNAME', S13], ['SENTYPE', S7],
['ISMAXNEXCL', SNG], ['ISMAXFEXCL', SNG],
['MININ', SNG], ['MAXIN', SNG],
['IEXCPFS', SNG], ['IEXCMFS', SNG],
['RCONV', SNG], ['OUTSIGTYPE', S7],
['MINOUT', SNG], ['MAXOUT', SNG],
['OSCALPT', SNG], ['GNCALPT', SNG], ['CALTOL', SNG],
['VEXC', SNG], ['VEXCACC', SNG], ['EXCLOAD', SNG],
['EXCLOADREG', SNG], ['EXCIMAX', SNG],
['LINEAR', SNG], ['ACCURACY', SNG],
['TESTFREQ', INT], ['ATTEN', INT], ['ATTENTOL', INT],
['STEPRMIN', SNG], ['STEPRMAX', SNG],
['PSS', SNG], ['OUTNOISE', SNG], ['INPUTRES', SNG],
['OPENTC', SNG], ['LEADRERR', SNG],
['LINEARIZED', INT], ['OSCALIN', SNG], ['GNCALIN', SNG],
['BANDWIDTH', SNG], ['MINVS', SNG], ['MAXVS', SNG],
];
// SCM7B: 170 bytes/record
const S17 = 'STRING17';
const SCM7B_FIELDS = [
['MODNAME', S17], ['SENTYPE', S7],
['MINVS', SNG], ['NOMVS', SNG], ['MAXVS', SNG],
['VLIM', SNG], ['ILIM', SNG], ['PE', SNG],
['ISMAXNEXCL', SNG],
['MININ', SNG], ['MAXIN', SNG],
['MINOUT', SNG], ['MAXOUT', SNG],
['IEXC', SNG], ['EXCIMIN', SNG], ['EXCIMAX', SNG],
['LEADRERR', SNG], ['RCONV', SNG],
['OSCALPT', SNG], ['GNCALPT', SNG], ['CALTOL', SNG],
['ISMAXFEXCL', SNG],
['VEXC', SNG], ['VEXCLO', SNG], ['VEXCHI', SNG],
['LOOPIMAX', SNG],
['LINEAR', SNG], ['ACCURACY', SNG], ['PSS', SNG],
['TESTFREQ', INT], ['ATTEN', INT], ['ATTENTOL', INT],
['STEPRESP', SNG], ['STEPTOL', SNG],
['OUTNOISERMS', SNG], ['OUTNOISEVPK', SNG],
['INPUTRES', SNG], ['VOPENTC', SNG],
['CJCACC', SNG], ['IBIAS', SNG],
];
// --------------------------------------------------------------------------
// Record size calculation
// --------------------------------------------------------------------------
function calcRecordSize(fields) {
let size = 0;
for (const [, type] of fields) {
size += FIELD_TYPES[type].size;
}
return size;
}
// --------------------------------------------------------------------------
// Parse a single record from a buffer
// --------------------------------------------------------------------------
function parseRecord(buf, offset, fields) {
const record = {};
let pos = offset;
for (const [name, type] of fields) {
const ft = FIELD_TYPES[type];
record[name] = ft.read(buf, pos);
pos += ft.size;
}
return record;
}
// --------------------------------------------------------------------------
// Parse an entire DAT file into an array of records
// --------------------------------------------------------------------------
function parseDatFile(filePath, fields) {
if (!fs.existsSync(filePath)) {
console.error(`Spec file not found: ${filePath}`);
return [];
}
const buf = fs.readFileSync(filePath);
const recordSize = calcRecordSize(fields);
const numRecords = Math.floor(buf.length / recordSize);
const records = [];
for (let i = 0; i < numRecords; i++) {
const offset = i * recordSize;
if (offset + recordSize > buf.length) break;
const record = parseRecord(buf, offset, fields);
// Skip records with empty, placeholder, or corrupted model names
const modname = record.MODNAME;
if (!modname || modname.length === 0) continue;
// Skip if model name contains non-alphanumeric characters (except dash)
if (!/^[A-Za-z0-9-]+$/.test(modname)) continue;
// Skip placeholder entries
if (/^[XYZ]+$/.test(modname) || /^ZZZZ/.test(modname)) continue;
// Skip if MODNAME doesn't start with a known product prefix
const upper = modname.toUpperCase();
if (!upper.match(/^(SCM5B|5B|SCM7B|7B|8B|DSCA|DSCT|SCT|BOGUS)/)) continue;
records.push(record);
}
return records;
}
// --------------------------------------------------------------------------
// Family configuration
// --------------------------------------------------------------------------
const FAMILIES = {
SCM5B: {
file: '5BMAIN.DAT',
fields: SCM5B_FIELDS,
family: 'SCM5B',
logType: '5BLOG',
},
B8: {
file: '8BMAIN.DAT',
fields: B8_FIELDS,
family: '8B',
logType: '8BLOG',
},
DSCA: {
file: 'DSCOUT.DAT',
fields: DSCA_FIELDS,
family: 'DSCA',
logType: 'DSCLOG',
},
DSCT: {
file: 'SCTMAIN.DAT',
fields: DSCT_FIELDS,
family: 'DSCT',
logType: 'SCTLOG',
},
DSCA_DIN: {
file: 'DSCMAIN4.DAT',
fields: DSCA_DIN_FIELDS,
family: 'DSCA',
logType: 'DSCLOG',
},
SCM5B45: {
file: '5B45DATA.DAT',
fields: SCM5B45_FIELDS,
family: 'SCM5B',
logType: '5BLOG',
},
SCM5B48: {
file: 'DB5B48.DAT',
fields: SCM5B48_FIELDS,
family: 'SCM5B',
logType: '5BLOG',
},
SCM5B49: {
file: '5B49_2.DAT',
fields: SCM5B49_FIELDS,
family: 'SCM5B',
logType: '5BLOG',
},
SCM7B: {
file: '7BMAIN.DAT',
fields: SCM7B_FIELDS,
family: 'SCM7B',
logType: '7BLOG',
},
};
// --------------------------------------------------------------------------
// Main API: load all specs into a lookup map
// --------------------------------------------------------------------------
/**
* Load all model specs from binary DAT files.
* @param {string} specDir - Directory containing the DAT files
* @returns {Map<string, object>} Map of model_number -> spec record (with _family added)
*/
function loadAllSpecs(specDir) {
specDir = specDir || DEFAULT_SPEC_DIR;
const specMap = new Map();
for (const [familyKey, config] of Object.entries(FAMILIES)) {
const filePath = path.join(specDir, config.file);
const records = parseDatFile(filePath, config.fields);
for (const record of records) {
record._family = config.family;
record._logType = config.logType;
// Normalize model name for lookup (trim, uppercase)
const key = record.MODNAME.toUpperCase().trim();
specMap.set(key, record);
}
console.log(`[SPEC] Loaded ${records.length} models from ${config.file} (${config.family})`);
}
console.log(`[SPEC] Total models loaded: ${specMap.size}`);
return specMap;
}
/**
* Look up specs for a model number.
* Tries exact match, then common prefix variations (SCM5B <-> 5B, DSCA <-> DSC).
* @param {Map} specMap - Spec map from loadAllSpecs()
* @param {string} modelNumber - Model number to look up
* @returns {object|null} Spec record or null
*/
function getSpecs(specMap, modelNumber) {
if (!modelNumber) return null;
const key = modelNumber.toUpperCase().trim();
// Exact match
if (specMap.has(key)) return specMap.get(key);
// Try adding/removing SCM prefix: "5B41-03" <-> "SCM5B41-03"
if (key.startsWith('SCM5B')) {
const short = key.replace('SCM5B', '5B');
if (specMap.has(short)) return specMap.get(short);
} else if (key.startsWith('5B')) {
const full = 'SCM' + key;
if (specMap.has(full)) return specMap.get(full);
}
// Try adding/removing SCM prefix for 7B
if (key.startsWith('SCM7B')) {
const short = key.replace('SCM7B', '7B');
if (specMap.has(short)) return specMap.get(short);
} else if (key.startsWith('7B')) {
const full = 'SCM' + key;
if (specMap.has(full)) return specMap.get(full);
}
// Try DSCA variations
if (key.startsWith('DSCA')) {
// Some specs stored without the 'A'
const short = key.replace('DSCA', 'DSC');
if (specMap.has(short)) return specMap.get(short);
}
// Try partial match on model base (before any suffix like C, D)
// e.g., "DSCA30-05C" -> try "DSCA30-05"
const baseMatch = key.match(/^(.+?)([A-Z])$/);
if (baseMatch) {
const base = baseMatch[1];
if (specMap.has(base)) return specMap.get(base);
// Also try with prefix variations
if (base.startsWith('SCM5B')) {
const short = base.replace('SCM5B', '5B');
if (specMap.has(short)) return specMap.get(short);
} else if (base.startsWith('5B')) {
if (specMap.has('SCM' + base)) return specMap.get('SCM' + base);
}
}
return null;
}
/**
* Determine product family from model number string
*/
function getFamily(modelNumber) {
if (!modelNumber) return null;
const m = modelNumber.toUpperCase();
if (m.startsWith('SCM5B') || m.startsWith('5B')) return 'SCM5B';
if (m.startsWith('SCM7B') || m.startsWith('7B')) return 'SCM7B';
if (m.startsWith('8B')) return '8B';
if (m.startsWith('DSCA')) return 'DSCA';
if (m.startsWith('DSCT') || m.startsWith('SCT')) return 'DSCT';
return null;
}
// --------------------------------------------------------------------------
// CLI: test the parser
// --------------------------------------------------------------------------
if (require.main === module) {
const specDir = process.argv[2] || DEFAULT_SPEC_DIR;
console.log(`Loading specs from: ${specDir}\n`);
const specMap = loadAllSpecs(specDir);
// Print a few examples from each family
const examples = {};
for (const [key, spec] of specMap) {
const fam = spec._family;
if (!examples[fam]) examples[fam] = [];
if (examples[fam].length < 3) {
examples[fam].push(spec);
}
}
for (const [fam, specs] of Object.entries(examples)) {
console.log(`\n--- ${fam} Examples ---`);
for (const s of specs) {
console.log(` ${s.MODNAME}: SENTYPE=${s.SENTYPE}, MININ=${s.MININ}, MAXIN=${s.MAXIN}, MINOUT=${s.MINOUT}, MAXOUT=${s.MAXOUT}`);
}
}
}
module.exports = { loadAllSpecs, getSpecs, getFamily, FAMILIES };

View File

@@ -0,0 +1,164 @@
/**
* Work Order Report Parser
*
* Parses the TXT work order status reports from TS-XX/Reports/ folders.
*
* Format:
* ===================================================================
* WO#: 179257
* Date: 03-27-2026
* Work order status file for work order #: 179257
* Program: TEST8B1D.EXE
* Version: B.19 2023.08.02 JL
* Lib. Ver.: B.09 2019.02.08 MR
* -------------------------------------------------------------------
* Status Serial# DS File Name Model Date Time
* -------- --------- ------------ ------------- ---------- --------
* PASS 179257-1 H9257-1.TXT 8B47K-05 03-27-2026 10:25:56
* FAIL<<<< 179257-12 8B47K-05 03-27-2026 11:01:09
* ...
*/
const fs = require('fs');
const path = require('path');
/**
* Extract test station from file path (e.g., C:\Shares\test\TS-4L\Reports\179257.TXT -> TS-4L)
*/
function extractStation(filePath) {
const match = filePath.match(/[\\\/](TS-[^\\\/]+)[\\\/]/i);
return match ? match[1].toUpperCase() : null;
}
/**
* Extract work order number from filename (e.g., 179257.TXT -> 179257)
*/
function extractWoFromFilename(filePath) {
const base = path.basename(filePath, path.extname(filePath));
return base;
}
/**
* Parse a work order report TXT file
* @param {string} filePath - Path to the report file
* @returns {object} Parsed work order with header and lines
*/
function parseWoReport(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split(/\r?\n/);
const result = {
wo_number: null,
wo_date: null,
program: null,
version: null,
lib_version: null,
station: extractStation(filePath),
source_file: filePath,
lines: [], // test result lines
ds_files: [], // datasheet files listed at bottom
};
let inHeader = true;
let inDsList = false;
for (const line of lines) {
const t = line.trim();
// Parse header fields
const woMatch = t.match(/^WO#:\s*(\S+)/);
if (woMatch) {
result.wo_number = woMatch[1];
continue;
}
const dateMatch = t.match(/^Date:\s*(\d{2}-\d{2}-\d{4})/);
if (dateMatch) {
const [month, day, year] = dateMatch[1].split('-');
result.wo_date = `${year}-${month}-${day}`;
continue;
}
const progMatch = t.match(/^Program:\s*(\S+)/);
if (progMatch) {
result.program = progMatch[1];
continue;
}
const verMatch = t.match(/^Version:\s*(.+)/);
if (verMatch) {
result.version = verMatch[1].trim();
continue;
}
const libMatch = t.match(/^Lib\. Ver\.:\s*(.+)/);
if (libMatch) {
result.lib_version = libMatch[1].trim();
continue;
}
// Detect separator lines
if (t.match(/^-{20,}$/)) {
inHeader = false;
continue;
}
// Skip header row
if (t.startsWith('Status') && t.includes('Serial#')) continue;
// Detect datasheet file list section
if (t.includes('datasheet files actually created')) {
inDsList = true;
continue;
}
if (inDsList) {
if (t.match(/^-+$/)) continue;
if (t.match(/^\S+\.TXT$/i)) {
result.ds_files.push(t);
}
continue;
}
// Parse test result lines
// PASS 179257-1 H9257-1.TXT 8B47K-05 03-27-2026 10:25:56
// FAIL<<<< 179257-12 8B47K-05 03-27-2026 11:01:09
if (!inHeader && t.length > 0) {
const passMatch = t.match(/^(PASS)\s+(\S+)\s+(\S+\.TXT)\s+(\S+)\s+(\d{2}-\d{2}-\d{4})\s+(\d{2}:\d{2}:\d{2})/i);
const failMatch = t.match(/^(FAIL[<]*)\s+(\S+)\s+(\S+)\s+(\d{2}-\d{2}-\d{4})\s+(\d{2}:\d{2}:\d{2})/i);
if (passMatch) {
const [, status, serial, dsFile, model, date, time] = passMatch;
const [month, day, year] = date.split('-');
result.lines.push({
status: 'PASS',
serial_number: serial,
ds_filename: dsFile,
model_number: model,
test_date: `${year}-${month}-${day}`,
test_time: time,
});
} else if (failMatch) {
const [, status, serial, model, date, time] = failMatch;
const [month, day, year] = date.split('-');
result.lines.push({
status: 'FAIL',
serial_number: serial,
ds_filename: null,
model_number: model,
test_date: `${year}-${month}-${day}`,
test_time: time,
});
}
}
}
// Fall back to filename for WO# if not found in content
if (!result.wo_number) {
result.wo_number = extractWoFromFilename(filePath);
}
return result;
}
module.exports = { parseWoReport, extractStation, extractWoFromFilename };

View File

@@ -0,0 +1,775 @@
/**
* Exact-Match Datasheet Formatter
*
* Generates TXT datasheets matching the original QuickBASIC DATASHEETWRITE output.
* Requires a DB record (with raw_data) and model specs from spec-reader.
*/
const { getFamily } = require('../parsers/spec-reader');
// -------------------------------------------------------------------------
// DATA LINES: parameter names and units per family
// -------------------------------------------------------------------------
const DATA_LINES = {
SCM5B: [
['Supply Current, Nom', 'mA'], // 1
['Supply Current, Max', 'mA'], // 2
['Exc. Current #1', 'uA'], // 3
['Exc. Current #2', 'uA'], // 4
['Exc. Current Match', 'uA'], // 5
['Output Resistance', 'ohms'], // 6
['CJC Gain', 'uV/C'], // 7
['Exc. Voltage', 'V'], // 8
['Exc. Load Reg.', 'ppm/mA'], // 9
['Vout Reg. w/ Load', '%'], // 10
['Exc. Current Limit', 'mA'], // 11
['Linearity', '%'], // 12
['Accuracy', '%'], // 13
['Lead R Effect', 'C/ohm'], // 14
['Supply Sensitivity', 'uV/%'], // 15
['Input Resistance', 'Mohms'], // 16
['Open Input Response', 'V'], // 17
['Frequency Response', 'dB'], // 18
['Step Response', '%'], // 19
['Output Noise', 'uVrms'], // 20
['Over-range Response', 'V'], // 21
],
'8B': [
['Supply Current, Nom', 'mA'],
['Supply Current, Max', 'mA'],
['Exc. Current #1', 'uA'],
['Exc. Current #2', 'uA'],
['Exc. Current Match', 'uA'],
['Output Resistance', 'ohms'],
['CJC Gain', 'uV/C'],
['Exc. Voltage', 'V'],
['Exc. Load Reg.', 'ppm/mA'],
['Vout Reg. w/ Load', '%'],
['Exc. Current Limit', 'mA'],
['Linearity', '%'],
['Accuracy', '%'],
['Lead R Effect', 'C/ohm'],
['Supply Sensitivity', 'ppm/%'],
['Input Resistance', 'Mohms'],
['Open Input Response', 'V'],
['Frequency Response', 'dB'],
['Step Response', '%'],
['Output Noise', 'uVrms'],
['Over-range Response', 'V'],
],
DSCA: [
['Supply Current, Nom', 'mA'],
['Supply Current @ Max Load', 'mA'],
['Linearity, 0mA Load', '%'],
['Accuracy, 0mA Load', '%'],
['Linearity, 5mA Load', '%'],
['Accuracy, 5mA Load', '%'],
['Linearity, 50mA Load', '%'],
['Accuracy, 50mA Load', '%'],
['Positive Current Limit', 'mA'],
['Negative Current Limit', 'mA'],
['Overrange', '%'],
['Power Supply Sensitivity', '%/%'],
['Input Resistance', 'Mohms'],
['Frequency Response', 'dB'],
['Step Response', '%'],
['Output Noise', ''],
['Compliance', '%'],
['Accuracy @ 5 ohm load', '%'],
],
SCM7B: [
['Supply Current', 'mA'], // 1
['Supply Current w/ Load', 'mA'], // 2
['Bias Current', 'nA'], // 3
['Input Resistance', 'kohms'], // 4
['Offset Calibration', 'mV'], // 5
['Gain Calibration', 'mV'], // 6
['Linearity/Conformity', '%'], // 7
['Accuracy', '%'], // 8
['VLoop @ 0 mA (Vs = 18V)', 'V'], // 9
[' (Vs = 35V)', 'V'], // 10
['VLoop @ 4 mA (Vs = 18V)', 'V'], // 11
[' (Vs = 35V)', 'V'], // 12
['VLoop @ 20mA (Vs = 18V)', 'V'], // 13
[' (Vs = 35V)', 'V'], // 14
['VLoop Peak Ripple', 'mV'], // 15
['High Excitation Current', 'uA'], // 16
['Low Excitation Current', 'uA'], // 17
['Output Effective Power', 'mW'], // 18
['Supply Sensitivity', '%/%Vs'], // 19
['Open Sensor Response', 'V'], // 20
['Lead Resistance Effect', 'C/ohm'], // 21
['CJC Gain', 'uV/C'], // 22
['100kHz Output Noise', 'uVrms'], // 23
['Attenuation', 'dB'], // 24
['150ms Step Response', 'V'], // 25
['Output Noise', 'mVpk'], // 26
['Over-Range', 'V'], // 27
['Under-Range', 'V'], // 28
['Open Loop Detect', 'mA'], // 29
['Error @ Max Rload', '%'], // 30
['Pass-Through Error', '%'], // 31
],
DSCT: [
['Under-range Limit', 'mA'],
['Over-range Limit', 'mA'],
['Error @ Vloop = 10.8V', '%'],
['Error @ Vloop = 60V', '%'],
['Minus f.s. Exc. Current', 'uA'],
['Plus f.s. Exc. Current', 'uA'],
['Current Source Matching', '%'],
['Linearity / Conformity', '%'],
['Accuracy', '%'],
['Lead Resistance Effects', 'C/ohm'],
['Loop Voltage Sensitivity', '%/V'],
['Input Resistance', 'Mohm'],
['Open Thermocouple Response', 'mA'],
['Frequency Response', 'dB'],
['Step Response', '%'],
['Output Noise', 'uArms'],
],
};
// -------------------------------------------------------------------------
// Sensor type number mapping (for input column headers)
// -------------------------------------------------------------------------
function getSensorNum(sentype) {
if (!sentype) return 1;
const s = sentype.toUpperCase().trim();
if (s === 'V' || s === 'MV') return 1;
if (s === 'MA') return 2;
if (s.includes('JTC') || s === 'J') return 3;
if (s.includes('KTC') || s === 'K') return 4;
if (s.includes('TTC') || s === 'T') return 5;
if (s.includes('ETC') || s === 'E' || s.includes('RTC') || s.includes('STC') || s.includes('NTC') || s.includes('BTC')) return 6;
if (s.includes('RTD')) return 7;
if (s === 'FBRIDGE' || s === 'HBRIDGE') return 8;
if (s === '2WTX') return 9;
return 1; // default voltage
}
// -------------------------------------------------------------------------
// Parse raw_data from DB record
// -------------------------------------------------------------------------
function parseRawData(rawData, family) {
if (!rawData) return null;
const lines = rawData.split('\n').map(l => l.trim()).filter(l => l.length > 0);
if (lines.length < 8) return null;
const result = {
modelLine: '',
accuracy: [], // 5 points: { stim, calc, meas, error, status }
stepResponse: 0,
statusEntries: [],
};
let lineIdx = 0;
// Line 0: model name (quoted)
result.modelLine = lines[lineIdx++].replace(/"/g, '').trim();
// Lines 1-5: accuracy points
for (let i = 0; i < 5 && lineIdx < lines.length; i++) {
const parts = parseCSVLine(lines[lineIdx++]);
if (parts.length >= 5) {
result.accuracy.push({
stim: parseFloat(parts[0]),
calc: parseFloat(parts[1]),
meas: parseFloat(parts[2]),
error: parseFloat(parts[3]),
status: parts[4].replace(/"/g, '').trim(),
});
}
}
// Next line: step response / placeholders
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;
}
// Remaining lines: STATUS groups
// SCM5B/8B: groups of 5, DSCT: groups of 4
const groupSize = (family === 'DSCT') ? 4 : 5;
while (lineIdx < lines.length) {
const line = lines[lineIdx];
// Stop if we hit the serial/date line
if (line.match(/^"\d+-\d+[A-Za-z]?","/)) break;
const parts = parseCSVLine(line);
for (const p of parts) {
result.statusEntries.push(p.replace(/"/g, ''));
}
lineIdx++;
}
return result;
}
// Simple CSV parser that handles quoted strings
function parseCSVLine(line) {
const parts = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const ch = line[i];
if (ch === '"') {
inQuotes = !inQuotes;
} else if (ch === ',' && !inQuotes) {
parts.push(current.trim());
current = '';
} else {
current += ch;
}
}
parts.push(current.trim());
return parts;
}
// -------------------------------------------------------------------------
// Format measured value from STATUS entry
// -------------------------------------------------------------------------
/**
* Format a number matching QuickBASIC STR$() behavior:
* - Positive numbers get a leading space
* - Leading zeros before decimal are dropped (0.03 -> .03)
* - Rounds to 6 significant digits to clean IEEE 754 artifacts
*/
function r(val, fixedDecimals) {
if (val == null || isNaN(val)) return '0';
const rounded = parseFloat(val.toPrecision(6));
let str;
if (fixedDecimals != null) {
str = rounded.toFixed(fixedDecimals);
} else {
str = String(rounded);
}
// QB STR$() drops leading zero: "0.03" -> ".03"
str = str.replace(/^0\./, '.').replace(/^-0\./, '-.');
// QB STR$() prepends space for positive numbers
if (rounded >= 0 && !str.startsWith(' ')) {
str = ' ' + str;
}
return str;
}
/**
* Parse STATUS$ entry and format measured value matching QB PRINT USING.
* QB format strings all produce exactly 6 characters for the number:
* "0" -> "###### &" (integer, 6 digits)
* "1" -> "####.# &" (1 decimal, 6 chars)
* "2" -> "####.# &" (same as 1)
* "3" -> "##.### &" (3 decimals, 6 chars)
* "4" -> "#.#### &" (4 decimals, 6 chars)
*/
function formatMeasured(statusStr) {
if (!statusStr || statusStr.length <= 4) return null;
const passFail = statusStr.substring(0, 4); // "PASS" or "FAIL"
const decimalDigit = statusStr[statusStr.length - 1];
const valueStr = statusStr.substring(5, statusStr.length - 1).trim();
const value = parseFloat(valueStr);
if (isNaN(value)) return { passFail, formatted: valueStr, width: 6 };
// QB PRINT USING: right-justified in 6 character positions
// Negative sign takes one digit position
let formatted;
switch (decimalDigit) {
case '0': formatted = Math.round(value).toString().padStart(6); break;
case '1': formatted = value.toFixed(1).padStart(6); break;
case '2': formatted = value.toFixed(1).padStart(6); break;
case '3': formatted = value.toFixed(3).padStart(6); break;
case '4': formatted = value.toFixed(4).padStart(6); break;
default: formatted = value.toFixed(1).padStart(6); break;
}
return { passFail, formatted, value };
}
// -------------------------------------------------------------------------
// Format TSPEC display string from spec values
// -------------------------------------------------------------------------
function buildTSpecs(specs, family, stepResponse) {
if (!specs) return [];
const tspecs = [];
if (family === 'SCM5B' || family === '8B') {
tspecs[1] = ' < ' + r(specs.ISMAXNEXCL);
tspecs[2] = ' < ' + r(specs.ISMAXFEXCL);
tspecs[3] = ' ' + r(specs.IEXC);
tspecs[4] = ' ' + r(specs.IEXC);
const imatchtol = (specs.IMATCHTOL || 0) / 100;
tspecs[5] = '+/-' + r(specs.IEXC * imatchtol, 0);
tspecs[6] = family === '8B' ? ' < 50' : ' < ' + r(specs.OUTRES || 55);
tspecs[7] = ''; // CJC gain - computed from polynomial, skip for now
if (specs.VEXC) {
const vexcAcc = Math.round(specs.VEXCACC / 100 * specs.VEXC * 1000) / 1000;
tspecs[8] = r(specs.VEXC, 1) + '+/-' + r(vexcAcc, 3);
} else {
tspecs[8] = '';
}
tspecs[9] = '+/-' + r(specs.EXCLOADREG);
const acc125 = Math.round((specs.ACCURACY * 1.25) * 100) / 100;
tspecs[10] = '+/-' + r(acc125);
tspecs[11] = ' < ' + r(specs.EXCIMAX);
tspecs[12] = '+/-' + r(specs.LINEAR);
tspecs[13] = '+/-' + r(specs.ACCURACY);
tspecs[14] = '+/-' + r(stepResponse || 0, 1);
tspecs[15] = '+/-' + r(specs.PSS || 0);
tspecs[16] = ' >=' + r(specs.INPUTRES);
if (specs.VOPENINMIN != null && specs.VOPENINMAX != null) {
tspecs[17] = r(specs.VOPENINMIN, 2) + ' to ' + r(specs.VOPENINMAX, 2);
} else {
tspecs[17] = '';
}
tspecs[18] = r(specs.ATTEN) + '+/-' + r(specs.ATTENTOL);
tspecs[19] = r(specs.STEPRMIN || 0) + ' to ' + r(specs.STEPRMAX || 0);
tspecs[20] = ' < ' + r(specs.OUTNOISE);
tspecs[21] = tspecs[17]; // duplicate
} else if (family === 'DSCA') {
tspecs[1] = ' < ' + r(specs.ISMAXNL || 0);
tspecs[2] = ' < ' + r(specs.ISMAXFL || 0);
tspecs[3] = '+/-' + r(specs.LINEAR1 || 0);
tspecs[4] = '+/-' + r(specs.ACCURACY1 || 0);
tspecs[5] = '+/-' + r(specs.LINEAR2 || 0);
tspecs[6] = '+/-' + r(specs.ACCURACY2 || 0);
tspecs[7] = '+/-' + r(specs.LINEAR3 || 0);
tspecs[8] = '+/-' + r(specs.ACCURACY3 || 0);
tspecs[9] = ' < ' + r(specs.ILIMIT || 0);
tspecs[10] = ' > ' + r(-(specs.ILIMIT || 0));
tspecs[11] = ' > ' + r(specs.PERCOVER || 0);
tspecs[12] = '+/-' + r(specs.PSS || 0);
tspecs[13] = ' >=' + r(specs.INPUTRES || 0);
tspecs[14] = r(specs.ATTEN || 0) + '+/-' + r(specs.ATTENTOL || 0);
tspecs[15] = r(specs.STEPRMIN || 0) + ' to ' + r(specs.STEPRMAX || 0);
tspecs[16] = ' <=' + r(specs.OUTNOISE || 0);
tspecs[17] = '+/-' + r(specs.COMPLIANCE || 0);
tspecs[18] = '+/-' + r((specs.ACCURACY1 || 0) * 2);
} else if (family === 'DSCT') {
tspecs[1] = ''; // computed at runtime
tspecs[2] = ''; // computed at runtime
tspecs[3] = ' < 1';
tspecs[4] = ' < 1';
const iexcmTol = specs.MODNAME && specs.MODNAME.startsWith('DSCT') ? 0.05 : 0.02;
tspecs[5] = Math.round(specs.IEXCMFS || 0) + '+/-' + Math.round((specs.IEXCMFS || 0) * iexcmTol);
tspecs[6] = Math.round(specs.IEXCPFS || 0) + '+/-' + Math.round((specs.IEXCPFS || 0) * iexcmTol);
tspecs[7] = '+/-' + r(specs.IMATCHTOL || 0);
tspecs[8] = '+/- ' + r(specs.LINEAR || 0);
tspecs[9] = '+/- ' + r(specs.ACCURACY || 0);
tspecs[10] = '+/-' + r(stepResponse || 0, 1);
tspecs[11] = '+/-' + r(specs.VSEN || 0);
tspecs[12] = ' >=' + r(specs.INPUTRES || 0);
const iopentc = specs.IOPENTC || 0;
const maxout = specs.MAXOUT || 20;
tspecs[13] = (iopentc > maxout ? ' > ' : ' < ') + r(iopentc);
tspecs[14] = r(specs.ATTEN || 0) + '+/-' + r(specs.ATTENTOL || 0);
tspecs[15] = r(specs.STEPRMIN || 0) + ' to ' + r(specs.STEPRMAX || 0);
tspecs[16] = ' < ' + r(specs.OUTNOISE || 0);
} else if (family === 'SCM7B') {
const orange = (specs.MAXOUT || 5) - (specs.MINOUT || 0);
tspecs[1] = '< ' + r(specs.ISMAXNEXCL + 6);
tspecs[2] = '< ' + r(specs.ISMAXFEXCL + 6);
tspecs[3] = '+/-' + r(specs.IBIAS || 0);
tspecs[4] = ' > ' + r(specs.INPUTRES || 0);
const calTol = 20 * orange * (specs.CALTOL || 0);
tspecs[5] = '+/-' + r(calTol);
tspecs[6] = '+/-' + r(calTol);
tspecs[7] = '+/-' + r(specs.LINEAR || 0);
tspecs[8] = '+/-' + r(specs.ACCURACY || 0);
if (specs.VEXC) {
const vexc5 = specs.VEXC * 0.05;
tspecs[9] = r(specs.VEXC) + ' +/-' + r(vexc5);
tspecs[10] = tspecs[9];
}
if (specs.VEXCLO) {
const vlo5 = specs.VEXCLO * 0.05;
tspecs[11] = r(specs.VEXCLO) + ' +/-' + r(vlo5);
tspecs[12] = tspecs[11];
}
if (specs.VEXCHI) {
const vhi5 = specs.VEXCHI * 0.05;
tspecs[13] = r(specs.VEXCHI) + ' +/-' + r(vhi5);
tspecs[14] = tspecs[13];
}
tspecs[15] = ' < 50';
tspecs[16] = ' < ' + r(specs.EXCIMAX || 0);
tspecs[17] = ' > ' + r(specs.EXCIMIN || 0);
tspecs[18] = ' > ' + r(specs.PE || 0);
tspecs[19] = '+/-' + r(specs.PSS || 0);
tspecs[20] = ''; // Open TC - needs runtime calc
tspecs[21] = '+/-' + r(specs.LEADRERR || 0);
tspecs[22] = ''; // CJC - needs seebeck polynomial
tspecs[23] = ' < ' + r(specs.OUTNOISERMS || 0);
tspecs[24] = r(specs.ATTEN || 0) + '+/-' + r(specs.ATTENTOL || 0);
// Step response
if (specs.STEPRESP && specs.STEPTOL) {
const lowV = specs.STEPRESP - specs.STEPTOL;
const highV = specs.STEPRESP + specs.STEPTOL;
tspecs[25] = r(lowV) + ' to ' + r(highV);
} else {
tspecs[25] = '';
}
tspecs[26] = ' < ' + r(specs.OUTNOISEVPK || 0);
tspecs[27] = '+5 to +5.8';
tspecs[28] = '-.9 to +1';
tspecs[29] = '0';
tspecs[30] = ''; // Compliance - needs runtime calc
tspecs[31] = '+/-' + r(specs.ACCURACY || 0);
}
return tspecs;
}
// -------------------------------------------------------------------------
// Format accuracy value based on sensor type
// -------------------------------------------------------------------------
function formatAccuracyLine(point, sensorNum, maxIn) {
let stimStr;
if (sensorNum >= 3 && sensorNum <= 6) {
// Temperature: +####.##
stimStr = formatSigned(point.stim, 2, 8);
} else if (sensorNum === 7) {
// Resistance: #####.##
stimStr = point.stim.toFixed(2).padStart(8);
} else {
// Voltage/Current: +###.###
const scale = (maxIn != null && maxIn < 1) ? 1000 : 1;
stimStr = formatSigned(point.stim * scale, 3, 8);
}
const calcStr = formatSigned(point.calc, 3, 7);
const measStr = formatSigned(point.meas, 3, 7);
const errorStr = formatSigned(point.error, 3, 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.
*/
function setCol(str, col, text) {
while (str.length < col) str += ' ';
return str + text;
}
/**
* Pad string to reach a column position (for inline TAB simulation).
* Returns spaces needed to reach the column from current position.
*/
function padToCol(str, col) {
const needed = col - str.length;
return needed > 0 ? ' '.repeat(needed) : ' ';
}
function formatSigned(val, decimals, width) {
const sign = val >= 0 ? '+' : '';
const str = sign + val.toFixed(decimals);
return str.padStart(width);
}
// -------------------------------------------------------------------------
// Main: generate exact-match TXT datasheet
// -------------------------------------------------------------------------
/**
* Generate an exact-match TXT datasheet from a DB record and model specs.
* @param {object} record - DB record with raw_data, model_number, serial_number, test_date
* @param {object} specs - Model spec record from spec-reader
* @returns {string|null} Formatted TXT datasheet, or null if data is insufficient
*/
function generateExactDatasheet(record, specs) {
const family = getFamily(record.model_number);
if (!family) return null;
const parsed = (family === 'SCM7B')
? parse7BRawData(record.raw_data)
: parseRawData(record.raw_data, family);
if (!parsed) return null;
if (family !== 'SCM7B' && parsed.accuracy.length < 5) return null;
const dataLines = DATA_LINES[family];
if (!dataLines) return null;
const sentype = specs ? specs.SENTYPE : '';
const sensorNum = getSensorNum(sentype);
const maxIn = specs ? specs.MAXIN : 10;
const tspecs = specs ? buildTSpecs(specs, family, parsed.stepResponse) : [];
// Format test date from YYYY-MM-DD to MM-DD-YYYY
const dateParts = (record.test_date || '').split('-');
const dateStr = dateParts.length === 3
? `${dateParts[1]}-${dateParts[2]}-${dateParts[0]}`
: record.test_date || '';
let modelName = specs ? specs.MODNAME : record.model_number;
// 7B header prepends "SCM" to the model name
if (family === 'SCM7B' && !modelName.toUpperCase().startsWith('SCM')) {
modelName = 'SCM' + modelName;
}
const lines = [];
const TAB5 = ' '; // 4 spaces = TAB(5) in QB (0-indexed)
// ---- Header ----
lines.push(TAB5 + 'DATAFORTH CORPORATION Phone: (520) 741-1404');
lines.push(TAB5 + '3331 E. Hemisphere Loop Fax: (520) 741-0762');
lines.push(TAB5 + 'Tucson, AZ 85706 USA email: info@dataforth.com');
lines.push('');
lines.push(' TEST DATA SHEET');
lines.push(TAB5 + '~'.repeat(71));
// QB: PRINT #9, TAB(5); "Date: "; DATE$
// PRINT #9, TAB(5); "Model: "; SPECS.MODNAME
// PRINT #9, TAB(5); "SN: "; TAB(12); SN$
lines.push(TAB5 + 'Date: ' + dateStr);
lines.push(TAB5 + 'Model: ' + modelName);
let snLine = TAB5 + 'SN: ';
snLine = setCol(snLine, 11, record.serial_number); // TAB(12) = index 11
lines.push(snLine);
lines.push('');
// ---- Accuracy Test ----
// 7B CSV format doesn't include individual accuracy test points (only error pcts in LOGIT)
// The accuracy data is only in the SHT files, not the DAT files
if (family === 'SCM7B') {
// Skip accuracy section entirely for 7B — data not available from DAT format
} else {
lines.push(' ACCURACY TEST');
lines.push('');
lines.push(' Calculated Measured');
// Input column header based on sensor type
let inputHeader;
if (sensorNum >= 3 && sensorNum <= 6) {
inputHeader = ' Temp. (C)';
} else if (sensorNum === 2 || sensorNum === 9) {
inputHeader = ' Iin (mA)';
} else if (sensorNum === 7) {
inputHeader = ' Rin (ohms)';
} else {
inputHeader = (maxIn != null && maxIn < 1) ? ' Vin (mV)' : ' Vin (V)';
}
lines.push(' ' + inputHeader + ' Vout (V) Vout (V)* Error (%) Status');
lines.push(TAB5 + '========== ========== ========== ========= ========');
for (const point of parsed.accuracy) {
lines.push(formatAccuracyLine(point, sensorNum, maxIn));
}
lines.push('');
} // end accuracy section conditional
// ---- Final Test Results ----
// QB column positions (1-indexed): TAB(31), TAB(47), TAB(60-speclen), TAB(61), TAB(71)
lines.push(' FINAL TEST RESULTS');
lines.push('');
// 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');
hdr1 = setCol(hdr1, 50, 'Specification ');
hdr1 = setCol(hdr1, 69, 'Status');
lines.push(hdr1);
// QB: TAB(5); "======================="; TAB(30); "==============="; TAB(47); "====================="; TAB(70); "======"
let hdr2 = setCol('', 4, '=======================');
hdr2 = setCol(hdr2, 29, '===============');
hdr2 = setCol(hdr2, 46, '=====================');
hdr2 = setCol(hdr2, 69, '======');
lines.push(hdr2);
for (let i = 0; i < dataLines.length && i < parsed.statusEntries.length; i++) {
const status = parsed.statusEntries[i];
if (!status || status.length <= 4) continue; // Skip if no measured data
const [paramName, paramUnit] = dataLines[i];
let unit = paramUnit;
// Unit overrides per QB logic
if (family === 'SCM5B' || family === '8B') {
if (i === 13 && sensorNum === 7) unit = 'ohm/ohm';
if (i === 14 && (sensorNum === 5 || sensorNum === 6)) unit = 'C/V';
}
const measured = formatMeasured(status);
if (!measured) continue;
// Build line matching QB TAB positions (converting to 0-indexed for string ops)
// TAB(5): parameter name
// TAB(31): measured value (6 chars right-justified) + space + unit
// TAB(60-speclen): spec string right-aligned to end at col 60
// TAB(61): unit
// TAB(71): PASS/FAIL
let line = '';
line = setCol(line, 4, paramName); // TAB(5) = index 4
line = setCol(line, 30, measured.formatted + ' ' + unit); // TAB(31) = index 30
const tspec = tspecs[i + 1]; // 1-indexed in TSPECS
if (tspec) {
const specLen = tspec.length;
line = setCol(line, 59 - specLen, tspec); // TAB(60-speclen)
line = setCol(line, 60, unit); // TAB(61) = index 60
}
line = setCol(line, 70, measured.passFail); // TAB(71) = index 70
lines.push(line);
}
// ---- Footer ----
// 240 VAC / Hi-Pot (conditional by family/model)
if (family === 'SCM5B') {
const mn = (modelName || '').trim();
if (!mn.startsWith('SCM5BPT') && !mn.startsWith('SCM5B-1369')) {
lines.push(TAB5 + '240 VAC Withstand' + ''.padEnd(49) + 'PASS');
lines.push(TAB5 + 'Hi-Pot' + ''.padEnd(60) + 'PASS');
}
} else if (family === '8B') {
const mn = (modelName || '').trim();
if (!mn.startsWith('8BPT')) {
lines.push(TAB5 + 'VAC Withstand' + ''.padEnd(53) + 'PASS');
lines.push(TAB5 + 'Hi-Pot' + ''.padEnd(60) + 'PASS');
}
} else if (family === 'SCM7B') {
const mn = (modelName || '').toUpperCase();
if (!mn.includes('7BPT')) {
let vac = setCol(TAB5 + '120VAC Withstand', 70, 'PASS');
lines.push(vac);
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') {
const mn = (modelName || '').toUpperCase();
if (!mn.startsWith('SCMHVAS')) {
lines.push(TAB5 + '240 VAC Withstand' + ''.padEnd(49) + 'PASS');
lines.push(TAB5 + 'Hi-Pot' + ''.padEnd(60) + 'PASS');
}
}
// Underline + Check List
lines.push(TAB5 + '_'.repeat(71));
if (family === 'SCM7B') {
lines.push(' Packing Check List');
lines.push('');
lines.push(setCol(TAB5 + 'Module Appearance: _____', 44, 'Mounting Screw: _____'));
lines.push('');
lines.push(setCol(TAB5 + 'Pins Straight: _____', 44, 'Module Header: _____'));
lines.push('');
lines.push(setCol(TAB5 + 'Tested by: _____________', 44, 'QC: _______________'));
} else if (family !== 'DSCA') {
lines.push(' Check List');
lines.push('');
lines.push(setCol(TAB5 + 'Module Appearance: __X__', 44, 'Mounting Screw: __X__'));
lines.push('');
lines.push(setCol(TAB5 + 'Pins Straight: __X__', 44, 'Module Header: __X__'));
}
// DSCA current output load note
if (family === 'DSCA' && specs && specs.OUTSIGTYPE && specs.OUTSIGTYPE.trim().toUpperCase() === 'CURRENT') {
lines.push(TAB5 + 'Standard output load for test is 250 ohms.');
}
lines.push('');
lines.push(TAB5 + 'It is hereby certified that the above product is in conformance with');
lines.push(TAB5 + 'all requirements to the extent specified. This product is not');
lines.push(TAB5 + 'authorized or warranted for use in life support devices and/or systems.');
lines.push('');
lines.push(TAB5 + '* NIST traceable calibration certificates support Measured Value data.');
lines.push(TAB5 + ' Calibration services are available through ANSI/NCSL Z540-1 and');
lines.push(TAB5 + ' ISO Guide 25 Certified Metrology Labs.');
lines.push('');
return lines.join('\r\n');
}
/**
* Parse 7B raw_data (single CSV line format)
* Format: STAGE: MODEL,SN,DATE,VERSION,DMMSERIAL,val1,...val31,err1,...errN
* val=9999 means not tested, [val] means FAIL
*/
function parse7BRawData(rawData) {
if (!rawData) return null;
const match = rawData.match(/^([A-Z-]+):\s*(.*)$/);
if (!match) return null;
const parts = match[2].split(',');
if (parts.length < 36) return null; // model + sn + date + version + dmmserial + 31 values minimum
const result = {
modelLine: parts[0].trim(),
accuracy: [],
stepResponse: 0,
statusEntries: [],
};
// Values start at index 5 (after model, sn, date, version, dmmserial)
for (let i = 0; i < 31; i++) {
const rawVal = (parts[5 + i] || '').trim();
if (rawVal === '9999' || rawVal === '') {
// Not tested - push short "PASS" (will be skipped by formatter)
result.statusEntries.push('PASS');
} else if (rawVal.startsWith('[')) {
// FAIL - bracketed value
const val = rawVal.replace(/[\[\]]/g, '').trim();
const numVal = parseFloat(val);
if (isNaN(numVal) || numVal === 0) {
result.statusEntries.push('FAIL');
} else {
const decimals = guessDecimals(numVal);
result.statusEntries.push('FAIL ' + val + decimals);
}
} else {
// PASS with value
const numVal = parseFloat(rawVal);
if (isNaN(numVal)) {
result.statusEntries.push('PASS');
} else {
const decimals = guessDecimals(numVal);
result.statusEntries.push('PASS ' + rawVal.trim() + decimals);
}
}
}
// Error percentages follow the 31 values - these are the accuracy test point errors
const errorStart = 5 + 31;
for (let i = errorStart; i < parts.length; i++) {
const val = parseFloat((parts[i] || '').trim());
if (!isNaN(val)) {
result.accuracy.push({
stim: 0, // Stimulus not stored in 7B CSV format
calc: 0,
meas: 0,
error: val * 100, // Convert fraction to percentage
status: 'PASS',
});
}
}
return result;
}
/**
* Guess the decimal format digit based on value magnitude
*/
function guessDecimals(val) {
const abs = Math.abs(val);
if (abs === 0) return '0';
if (abs >= 100) return '0';
if (abs >= 10) return '1';
if (abs >= 1) return '1';
if (abs >= 0.1) return '3';
return '4';
}
module.exports = { generateExactDatasheet, parseRawData, parse7BRawData, DATA_LINES };

View File

@@ -0,0 +1,205 @@
/**
* Datasheet Generator
* Generates formatted test data sheets from database records
*/
/**
* Generate a datasheet from a test record
* @param {Object} record - Database record
* @param {string} format - Output format ('html' or 'txt')
* @returns {string} Formatted datasheet
*/
function generateDatasheet(record, format = 'html') {
if (format === 'html') {
return generateHtmlDatasheet(record);
} else {
return generateTextDatasheet(record);
}
}
/**
* Generate HTML datasheet
*/
function generateHtmlDatasheet(record) {
const testDate = formatDate(record.test_date);
return `<!DOCTYPE html>
<html>
<head>
<title>Test Data Sheet - ${record.serial_number}</title>
<style>
body {
font-family: 'Courier New', monospace;
font-size: 12px;
max-width: 800px;
margin: 20px auto;
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.company {
font-weight: bold;
}
.contact {
text-align: right;
}
h1 {
text-align: center;
border-top: 2px solid #333;
border-bottom: 2px solid #333;
padding: 10px;
font-size: 16px;
}
.info {
margin: 20px 0;
}
.info-row {
display: flex;
}
.info-label {
width: 100px;
font-weight: bold;
}
.raw-data {
background: #f5f5f5;
padding: 15px;
border: 1px solid #ddd;
white-space: pre-wrap;
font-family: 'Courier New', monospace;
font-size: 11px;
overflow-x: auto;
}
.result {
font-size: 18px;
font-weight: bold;
text-align: center;
padding: 10px;
margin: 20px 0;
}
.result.pass { background: #d4edda; color: #155724; }
.result.fail { background: #f8d7da; color: #721c24; }
.footer {
margin-top: 30px;
border-top: 1px solid #333;
padding-top: 20px;
font-size: 10px;
}
@media print {
body { margin: 0; }
.no-print { display: none; }
}
</style>
</head>
<body>
<div class="header">
<div class="company">
DATAFORTH CORPORATION<br>
3331 E. Hemisphere Loop<br>
Tucson, AZ 85706 USA
</div>
<div class="contact">
Phone: (520) 741-1404<br>
Fax: (520) 741-0762<br>
Email: info@dataforth.com
</div>
</div>
<h1>TEST DATA SHEET</h1>
<div class="info">
<div class="info-row"><span class="info-label">Date:</span> ${testDate}</div>
<div class="info-row"><span class="info-label">Model:</span> ${record.model_number}</div>
<div class="info-row"><span class="info-label">SN:</span> ${record.serial_number}</div>
<div class="info-row"><span class="info-label">Log Type:</span> ${record.log_type}</div>
<div class="info-row"><span class="info-label">Station:</span> ${record.test_station || 'N/A'}</div>
</div>
<div class="result ${record.overall_result?.toLowerCase() || ''}">
OVERALL RESULT: ${record.overall_result || 'UNKNOWN'}
</div>
<h3>Test Data</h3>
<div class="raw-data">${escapeHtml(record.raw_data || 'No data available')}</div>
<div class="footer">
<p>It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.</p>
<p>Source: ${record.source_file}</p>
<p>Record ID: ${record.id}</p>
</div>
<div class="no-print" style="margin-top: 20px; text-align: center;">
<button onclick="window.print()">Print Datasheet</button>
<button onclick="window.close()">Close</button>
</div>
</body>
</html>`;
}
/**
* Generate plain text datasheet
*/
function generateTextDatasheet(record) {
const testDate = formatDate(record.test_date);
const line = '='.repeat(75);
const tilde = '~'.repeat(75);
return `DATAFORTH CORPORATION Phone: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
${tilde}
Date: ${testDate}
Model: ${record.model_number}
SN: ${record.serial_number}
Log Type: ${record.log_type}
Station: ${record.test_station || 'N/A'}
OVERALL RESULT: ${record.overall_result || 'UNKNOWN'}
${line}
TEST DATA
${line}
${record.raw_data || 'No data available'}
${line}
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
Source: ${record.source_file}
Record ID: ${record.id}
`;
}
/**
* Format date for display
*/
function formatDate(dateStr) {
if (!dateStr) return 'Unknown';
// Convert YYYY-MM-DD to MM-DD-YYYY
const [year, month, day] = dateStr.split('-');
return `${month}-${day}-${year}`;
}
/**
* Escape HTML special characters
*/
function escapeHtml(str) {
if (!str) return '';
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
module.exports = { generateDatasheet };

View File

@@ -0,0 +1,81 @@
"""Single-session SSH+SFTP batch fetcher for AD2 -> AD1 Engineering share."""
import os, sys, time, base64, paramiko
import subprocess, yaml as _yaml
HOST = '192.168.0.6'
USER = 'sysadmin'
def _pwd():
r = subprocess.run(['sops','-d','D:/vault/clients/dataforth/ad2.sops.yaml'],
capture_output=True, text=True, timeout=30, check=True)
return _yaml.safe_load(r.stdout)['credentials']['password'].replace('\\','')
PWD = _pwd()
LOCAL_ROOT = r'D:\claudetools\projects\dataforth-dos\datasheet-pipeline\scmvas-hvas-research'
REMOTE_BASE = r'\\AD1\Engineering\ENGR\ATE\High Voltage Input Module Test'
AD2_STAGE = r'C:\Users\sysadmin\Documents\scmvas_stage'
def connect():
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(HOST, username=USER, password=PWD, timeout=30, banner_timeout=30, look_for_keys=False, allow_agent=False)
return c
def run(c, cmd, timeout=120):
stdin, stdout, stderr = c.exec_command(cmd, timeout=timeout)
out = stdout.read().decode('utf-8', errors='replace')
err = stderr.read().decode('utf-8', errors='replace')
rc = stdout.channel.recv_exit_status()
return rc, out, err
def ps(c, command):
enc = base64.b64encode(command.encode('utf-16-le')).decode()
return run(c, f'powershell -NoProfile -EncodedCommand {enc}', timeout=300)
def copy_one(c, remote_src, stage_dir):
script = f'Copy-Item -LiteralPath "{remote_src}" -Destination "{stage_dir}\\" -Force -ErrorAction Stop; Write-Host "OK"'
rc, out, err = ps(c, script)
status = 'OK' if 'OK' in out and rc == 0 else f'FAIL: {err.strip() or out.strip()}'
print(f'[{status}] {os.path.basename(remote_src)}')
def main():
c = connect()
try:
ps(c, f'New-Item -ItemType Directory -Force -Path "{AD2_STAGE}" | Out-Null')
files = [
f'{REMOTE_BASE}\\TESTHV3.BAS',
f'{REMOTE_BASE}\\LIBATE3.BAS',
f'{REMOTE_BASE}\\DBHV.BAS',
f'{REMOTE_BASE}\\Readme.txt',
f'{REMOTE_BASE}\\HVDATA\\hvin.dat',
f'{REMOTE_BASE}\\HVDATA\\hvsort.dat',
f'{REMOTE_BASE}\\Released\\TESTHV3.BAS',
f'{REMOTE_BASE}\\Released\\NLIBATE3.BAS',
f'{REMOTE_BASE}\\Released\\TESTHV4.BAS',
f'{REMOTE_BASE}\\Released\\TESTHV3.MAK',
]
for f in files:
copy_one(c, f, AD2_STAGE)
# Rename to disambiguate source locations
rc, out, err = ps(c, f'Get-ChildItem -LiteralPath "{AD2_STAGE}" | Select-Object Name,Length | Format-Table -AutoSize | Out-String')
print('\n=== AD2 stage ===')
print(out)
# SFTP pull everything
out_dir = os.path.join(LOCAL_ROOT, 'source')
os.makedirs(out_dir, exist_ok=True)
sftp = c.open_sftp()
stage_posix = AD2_STAGE.replace('\\', '/')
for e in sftp.listdir(stage_posix):
sftp.get(f'{stage_posix}/{e}', os.path.join(out_dir, e))
print(f'[pulled] {e}')
sftp.close()
finally:
c.close()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,62 @@
"""Find and pull sample VASLOG / Engineering-Tested TXTs."""
import paramiko, base64, os, posixpath
import subprocess, yaml as _yaml
HOST = '192.168.0.6'
USER = 'sysadmin'
def _pwd():
r = subprocess.run(['sops','-d','D:/vault/clients/dataforth/ad2.sops.yaml'],
capture_output=True, text=True, timeout=30, check=True)
return _yaml.safe_load(r.stdout)['credentials']['password'].replace('\\','')
PWD = _pwd()
LOCAL_SAMPLES = r'D:\claudetools\projects\dataforth-dos\datasheet-pipeline\scmvas-hvas-research\samples'
os.makedirs(LOCAL_SAMPLES, exist_ok=True)
def connect():
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(HOST, username=USER, password=PWD, timeout=30, banner_timeout=30, look_for_keys=False, allow_agent=False)
return c
def ps(c, cmd, to=300):
enc = base64.b64encode(cmd.encode('utf-16-le')).decode()
stdin, stdout, stderr = c.exec_command(f'powershell -NoProfile -EncodedCommand {enc}', timeout=to)
return stdout.read().decode('utf-8', errors='replace'), stderr.read().decode('utf-8', errors='replace')
def main():
c = connect()
try:
# 1. Probe NAS share for VASLOG paths. NAS is accessed from AD2 as \\D2TESTNAS\test mounted somewhere
print('=== Check C:\\Shares\\test (NAS mirror) for VASLOG ===')
out, err = ps(c, r'''Get-ChildItem -LiteralPath 'C:\Shares\test' -Directory | Select-Object Name,LastWriteTime | Format-Table -AutoSize | Out-String''')
print(out)
print('=== Check NAS-side LOGS/VASLOG via C:\\Shares\\test ===')
for p in [r'C:\Shares\test\LOGS', r'C:\Shares\test\LOGS\VASLOG']:
out, err = ps(c, f'''if (Test-Path -LiteralPath '{p}') {{ Get-ChildItem -LiteralPath '{p}' -Force | Select-Object Name,Mode,Length,LastWriteTime | Format-Table -AutoSize | Out-String }} else {{ Write-Host 'MISSING: {p}' }}''')
print(f'--- {p} ---')
print(out)
# 2. Try under TS-3R directory inside the test share if stations upload their logs
print('=== Search for VASLOG anywhere in test share (recursive, limited) ===')
out, err = ps(c, r'''Get-ChildItem -LiteralPath 'C:\Shares\test' -Recurse -Directory -Force -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'VASLOG|vaslog' } | Select-Object FullName | Format-List | Out-String''')
print(out[:2000])
# 3. Also check NAS directly - see if we have access via UNC
print('=== NAS UNC probe ===')
out, err = ps(c, r'''if (Test-Path -LiteralPath '\\D2TESTNAS\test\LOGS\VASLOG') { Get-ChildItem -LiteralPath '\\D2TESTNAS\test\LOGS\VASLOG' -Force | Select-Object Name,Length,LastWriteTime | Format-Table -AutoSize | Out-String } else { Write-Host 'No direct UNC access' }''')
print(out)
# 4. Look up all STAGE locations where TS stations push TXT
print('=== TS station TXT upload points ===')
out, err = ps(c, r'''Get-ChildItem -LiteralPath 'C:\Shares\test' -Directory -Force | ForEach-Object { $n = $_.Name; try { $c = (Get-ChildItem -LiteralPath $_.FullName -File -Filter '*.txt' -ErrorAction SilentlyContinue).Count; Write-Host "$n : $c txt files" } catch {} }''')
print(out[:3000])
finally:
c.close()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,42 @@
"""Focused VASLOG probe."""
import paramiko, base64, os
import subprocess, yaml as _yaml
HOST='192.168.0.6'; USER='sysadmin'
def _pwd():
r = subprocess.run(['sops','-d','D:/vault/clients/dataforth/ad2.sops.yaml'],
capture_output=True, text=True, timeout=30, check=True)
return _yaml.safe_load(r.stdout)['credentials']['password'].replace('\\','')
PWD = _pwd()
LOCAL = r'D:\claudetools\projects\dataforth-dos\datasheet-pipeline\scmvas-hvas-research\samples'
os.makedirs(LOCAL, exist_ok=True)
def ps(c, cmd, to=300):
enc = base64.b64encode(cmd.encode('utf-16-le')).decode()
stdin, stdout, stderr = c.exec_command(f'powershell -NoProfile -EncodedCommand {enc}', timeout=to)
return stdout.read().decode('utf-8', errors='replace'), stderr.read().decode('utf-8', errors='replace')
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(HOST, username=USER, password=PWD, timeout=30, look_for_keys=False, allow_agent=False)
queries = [
('TS-3R root', r'Get-ChildItem -LiteralPath "C:\Shares\test\TS-3R" -Force | Select Name,Mode,LastWriteTime | Format-Table -AutoSize | Out-String'),
('TS-3R\\LOGS', r'if (Test-Path "C:\Shares\test\TS-3R\LOGS") { Get-ChildItem "C:\Shares\test\TS-3R\LOGS" -Force | Select Name,Mode,LastWriteTime | Format-Table -AutoSize | Out-String } else { "MISS" }'),
('TS-3R VASLOG', r'if (Test-Path "C:\Shares\test\TS-3R\LOGS\VASLOG") { Get-ChildItem "C:\Shares\test\TS-3R\LOGS\VASLOG" -Force | Select Name,Mode,Length,LastWriteTime | Format-Table -AutoSize | Out-String } else { "MISS VASLOG" }'),
('Corrected HVAS', r'Get-ChildItem "C:\Shares\test\Corrected HVAS Files" -Force -ErrorAction SilentlyContinue | Select Name,Mode,Length,LastWriteTime | Format-Table -AutoSize | Out-String'),
('STAGE sample', r'Get-ChildItem "C:\Shares\test\STAGE" -Filter *.TXT -File -ErrorAction SilentlyContinue | Select -First 20 Name,Length | Format-Table -AutoSize | Out-String'),
('Recurse VASLOG', r'Get-ChildItem "C:\Shares\test" -Recurse -Directory -Force -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "VASLOG|HVAS" } | Select FullName | Format-List | Out-String'),
]
try:
for label, q in queries:
print(f'\n=== {label} ===')
out, err = ps(c, q)
print(out[:3000])
if err: print('[stderr]', err[:500])
finally:
c.close()

View File

@@ -0,0 +1,83 @@
"""Pull samples from VASLOG, VASLOG - Engineering Tested, and Corrected HVAS Files."""
import paramiko, base64, os
import subprocess, yaml as _yaml
HOST='192.168.0.6'; USER='sysadmin'
def _pwd():
r = subprocess.run(['sops','-d','D:/vault/clients/dataforth/ad2.sops.yaml'],
capture_output=True, text=True, timeout=30, check=True)
return _yaml.safe_load(r.stdout)['credentials']['password'].replace('\\','')
PWD = _pwd()
LOCAL = r'D:\claudetools\projects\dataforth-dos\datasheet-pipeline\scmvas-hvas-research\samples'
os.makedirs(LOCAL, exist_ok=True)
os.makedirs(os.path.join(LOCAL, 'vaslog-dat'), exist_ok=True)
os.makedirs(os.path.join(LOCAL, 'vaslog-engtxt'), exist_ok=True)
os.makedirs(os.path.join(LOCAL, 'corrected-hvas'), exist_ok=True)
def ps(c, cmd, to=120):
enc = base64.b64encode(cmd.encode('utf-16-le')).decode()
stdin, stdout, stderr = c.exec_command(f'powershell -NoProfile -EncodedCommand {enc}', timeout=to)
return stdout.read().decode('utf-8', errors='replace')
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(HOST, username=USER, password=PWD, timeout=30, look_for_keys=False, allow_agent=False)
sftp = c.open_sftp()
try:
# List the Engineering-Tested TXT folder
print('=== VASLOG - Engineering Tested listing ===')
out = ps(c, r'Get-ChildItem "C:\Shares\test\TS-3R\LOGS\VASLOG\VASLOG - Engineering Tested" -Force | Select Name,Length,LastWriteTime | Format-Table -AutoSize | Out-String')
print(out)
# Pull all .DAT files from VASLOG (they're small, total ~250KB)
print('\n=== Pulling VASLOG .DAT files ===')
for name in ['HVAS-M01.DAT','HVAS-M02.DAT','HVAS-M03.DAT','HVAS-M04.DAT','HVAS-MPT.DAT',
'VAS-M100.DAT','VAS-M200.DAT','VAS-M300.DAT','VAS-M400.DAT',
'VAS-M500.DAT','VAS-M600.DAT','VAS-M650.DAT','VAS-M700.DAT','VAS-MPT.DAT']:
src = f'C:/Shares/test/TS-3R/LOGS/VASLOG/{name}'
dst = os.path.join(LOCAL, 'vaslog-dat', name)
try:
sftp.get(src, dst)
print(f' pulled {name}')
except Exception as e:
print(f' MISS {name}: {e}')
# Pull 5 sample Engineering Tested TXTs
print('\n=== Pulling VASLOG Engineering Tested TXTs (first 10) ===')
engtxt_dir_posix = 'C:/Shares/test/TS-3R/LOGS/VASLOG/VASLOG - Engineering Tested'
try:
entries = sftp.listdir(engtxt_dir_posix)
print(f' {len(entries)} entries, pulling first 10')
for name in entries[:10]:
try:
sftp.get(f'{engtxt_dir_posix}/{name}', os.path.join(LOCAL, 'vaslog-engtxt', name))
print(f' pulled {name}')
except Exception as e:
print(f' MISS {name}: {e}')
except Exception as e:
print(f' LIST FAIL: {e}')
# Pull 5 Corrected HVAS TXT samples
print('\n=== Pulling Corrected HVAS samples ===')
ch_dir_posix = 'C:/Shares/test/Corrected HVAS Files'
try:
entries = sorted(sftp.listdir(ch_dir_posix))
print(f' {len(entries)} entries, pulling first 5')
for name in entries[:5]:
try:
sftp.get(f'{ch_dir_posix}/{name}', os.path.join(LOCAL, 'corrected-hvas', name))
print(f' pulled {name}')
except Exception as e:
print(f' MISS {name}: {e}')
except Exception as e:
print(f' LIST FAIL: {e}')
finally:
sftp.close()
c.close()
print('\n=== DONE ===')

View File

@@ -0,0 +1,41 @@
"""Categorize PASS/FAIL lines across the 14 local VASLOG .DAT samples.
Goal: understand whether plain-decimal vs E-notation correlates with
file (model), date, or random distribution.
"""
import os, re
DAT_DIR = r'D:\claudetools\projects\dataforth-dos\datasheet-pipeline\scmvas-hvas-research\samples\vaslog-dat'
RE_PASS_SCI = re.compile(r'"(PASS|FAIL)\s*(-?\d+\.?\d*E[+-]?\d{2})(\d?)"', re.I)
RE_PASS_PLAIN = re.compile(r'"(PASS|FAIL)\s*(-?\.?\d+\.?\d*)"', re.I)
RE_SNDATE = re.compile(r'^"([^"]+)","(\d{2}-\d{2}-\d{4})"')
for fn in sorted(os.listdir(DAT_DIR)):
path = os.path.join(DAT_DIR, fn)
with open(path, 'r', encoding='latin-1') as f:
lines = [l.strip() for l in f if l.strip()]
sci = 0
plain = 0
other = 0
dates = []
model = None
for line in lines:
if line.startswith('"') and not line.startswith('"PASS') and not line.startswith('"FAIL') and ',' not in line and '0' not in line[1:3]:
if not model: model = line.replace('"','').strip()
m_snd = RE_SNDATE.match(line)
if m_snd:
dates.append(m_snd.group(2))
continue
# Only interested in lines that contain a PASS/FAIL status field (not the SN line)
if '"PASS' in line or '"FAIL' in line:
m_sci = RE_PASS_SCI.search(line)
m_plain = RE_PASS_PLAIN.search(line)
if m_sci: sci += 1
elif m_plain: plain += 1
else: other += 1
# Sort dates by year
dates_sorted = sorted(dates)
date_range = f'{dates_sorted[0]} .. {dates_sorted[-1]}' if dates_sorted else '-'
total = sci + plain + other
print(f'{fn:20s} model={model!r:18s} total={total:4d} sci={sci:4d} plain={plain:4d} other={other:4d} dates={date_range}')

View File

@@ -0,0 +1,92 @@
"""Parse hvin.dat based on TYPE DBASE from DBHV.BAS / TESTHV3.BAS.
Record layout (199 bytes each):
MODNAME STRING * 13
INTYPE STRING * 3
MININ SINGLE
MAXIN SINGLE
OUTSIGTYPE STRING * 7
MINOUT SINGLE
MAXOUT SINGLE
WAVESHPCAL STRING * 8
...42 SINGLEs total...
"""
import struct, sys
FIELDS = [
('MODNAME', 's', 13),
('INTYPE', 's', 3),
('MININ', 'f', 4),
('MAXIN', 'f', 4),
('OUTSIGTYPE', 's', 7),
('MINOUT', 'f', 4),
('MAXOUT', 'f', 4),
('WAVESHPCAL', 's', 8),
('FINCAL', 'f', 4),
('FINMIN', 'f', 4),
('FINMAX', 'f', 4),
('FINEXTMIN', 'f', 4),
('FINEXTMAX', 'f', 4),
('INPROTECT', 'f', 4),
('IOUTLIM', 'f', 4),
('VOUTLIM', 'f', 4),
('OUTRES', 'f', 4),
('OUTNOISE', 'f', 4),
('OSCALIN', 'f', 4),
('GNCALIN', 'f', 4),
('OSCALPT', 'f', 4),
('GNCALPT', 'f', 4),
('CALTOL', 'f', 4),
('ADJ', 'f', 4),
('LINEAR', 'f', 4),
('ACCSINCAL', 'f', 4),
('ACCSINSTD', 'f', 4),
('ACCSINEXT', 'f', 4),
('ACCCF12', 'f', 4),
('ACCCF23', 'f', 4),
('ACCCF34', 'f', 4),
('ACCCF45', 'f', 4),
('CMR', 'f', 4),
('STEPTIME', 'f', 4),
('STEPPERC', 'f', 4),
('STEPTOL', 'f', 4),
('LOOPVMIN', 'f', 4),
('LOOPVNOM', 'f', 4),
('LOOPVMAX', 'f', 4),
('MAXLOADR', 'f', 4),
('MINVS', 'f', 4),
('NOMVS', 'f', 4),
('MAXVS', 'f', 4),
('ISMIN', 'f', 4),
('ISMAX', 'f', 4),
('PSS', 'f', 4),
]
RECORD_SIZE = sum(sz for _, _, sz in FIELDS)
print(f'Computed record size: {RECORD_SIZE} bytes')
def parse_record(buf, off):
rec = {}
pos = off
for name, typ, sz in FIELDS:
chunk = buf[pos:pos+sz]
if typ == 's':
rec[name] = chunk.rstrip(b'\x00 ').decode('latin-1', errors='replace').strip()
else:
rec[name] = struct.unpack('<f', chunk)[0]
pos += sz
return rec
def main():
with open(sys.argv[1], 'rb') as f:
buf = f.read()
print(f'File size: {len(buf)} bytes, {len(buf)/RECORD_SIZE} records')
n = len(buf) // RECORD_SIZE
for i in range(n):
r = parse_record(buf, i * RECORD_SIZE)
if not r['MODNAME'] or not any(c.isalnum() for c in r['MODNAME']):
continue
print(f"#{i+1:02d} {r['MODNAME']!r:20s} IN={r['INTYPE']!r} OUT={r['OUTSIGTYPE']!r} {r['MININ']:+.3f} to {r['MAXIN']:+.3f} -> {r['MINOUT']:+.3f} to {r['MAXOUT']:+.3f} Vs={r['NOMVS']:.1f} Is={r['ISMAX']:.1f}mA")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-5
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.01% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-5
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.01% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-6
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.015% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-6
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.015% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-7
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy -0.011% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-7
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy -0.011% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-7
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy -0.011% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-7
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy -0.011% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-8
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.003% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-8
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.003% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-8
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.003% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-8
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.003% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-9
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.001% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-9
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.001% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-9
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.001% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,32 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04-09-2026
Model: SCMHVAS-M0700
SN: 179377-9
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.001% +/- 0.03% PASS
_______________________________________________________________________
Check List
Tested By: ____________ QC: ________________
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04/09/2026
Model: SCMHVAS-M0100
SN: 179379-9
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.007% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 02/16/2011
Model: SCMVAS-M700
SN: 66260-12
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.012% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 08-01-2024
Model: SCMHVAS-M0300
SN: 171087-1
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.004% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 08-07-2024
Model: SCMHVAS-M0300
SN: 171087-10
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.01% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 08-07-2024
Model: SCMHVAS-M0300
SN: 171087-11
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.011% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 08-07-2024
Model: SCMHVAS-M0300
SN: 171087-12
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.007% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 08-07-2024
Model: SCMHVAS-M0300
SN: 171087-13
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.006% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 04/09/2026
Model: SCMHVAS-M0100
SN: 179379-1
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.007% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,396 @@
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-7.005501E-033","","",""
"","","",""
"179379-1","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.992997E-033","","",""
"","","",""
"179379-2","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.301449E-023","","",""
"","","",""
"179379-3","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.298487E-023","","",""
"","","",""
"179379-4","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.988644E-033","","",""
"","","",""
"179379-5","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.920448E-043","","",""
"","","",""
"179379-6","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.988085E-033","","",""
"","","",""
"179379-7","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.498721E-023","","",""
"","","",""
"179379-3","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 2.099555E-023","","",""
"","","",""
"179379-8","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-7.013697E-033","","",""
"","","",""
"179379-9","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 2.987589E-033","","",""
"","","",""
"179379-10","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.997374E-033","","",""
"","","",""
"179379-11","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.994659E-033","","",""
"","","",""
"179379-12","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS .01599373","","",""
"","","",""
"179379-13","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-2.996437E-033","","",""
"","","",""
"179379-14","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.298943E-023","","",""
"","","",""
"179379-15","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.098672E-023","","",""
"","","",""
"179379-16","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.986045E-033","","",""
"","","",""
"179379-17","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-2.00728E-033","","",""
"","","",""
"179379-18","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.005922E-033","","",""
"","","",""
"179379-19","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.988948E-033","","",""
"","","",""
"179379-20","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-5.011353E-033","","",""
"","","",""
"179379-21","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.998683E-023","","",""
"","","",""
"179379-22","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.799585E-023","","",""
"","","",""
"179379-23","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-4.563481E-063","","",""
"","","",""
"179379-24","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS .01198773","","",""
"","","",""
"179379-25","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 2.987217E-033","","",""
"","","",""
"179379-26","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.96422E-043","","",""
"","","",""
"179379-27","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.989933E-033","","",""
"","","",""
"179379-28","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.992649E-033","","",""
"","","",""
"179379-29","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.399023E-023","","",""
"","","",""
"179379-30","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.988575E-033","","",""
"","","",""
"179379-31","04-09-2026"
"SCMHVAS-M0100 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.989746E-033","","",""
"","","",""
"179379-32","04-09-2026"

View File

@@ -0,0 +1,336 @@
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.000783E-033","","",""
"","","",""
"bri-1","08-23-2024"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.000596E-033","","",""
"","","",""
"169815-6","08-28-2024"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.492646E-033","","",""
"","","",""
"169815-7","08-28-2024"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-2.328306E-073","","",""
"","","",""
"169815-8","08-28-2024"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.005393E-043","","",""
"","","",""
"169815-9","08-28-2024"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.997352E-033","","",""
"","","",""
"169815-10","08-28-2024"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-7.498404E-033","","",""
"","","",""
"169815-11","08-28-2024"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.500226E-023","","",""
"","","",""
"169815-12","08-28-2024"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.498124E-033","","",""
"","","",""
"169815-13","08-28-2024"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.500011E-033","","",""
"","","",""
"169815-14","08-28-2024"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.549707E-023","","",""
"","","",""
"169815-15","08-28-2024"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.001125E-033","","",""
"","","",""
"169815-16","08-28-2024"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.494461E-033","","",""
"","","",""
"173821-1","02-12-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 8.999649E-033","","",""
"","","",""
"173821-2","02-12-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.498047E-033","","",""
"","","",""
"173821-3","02-12-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.498047E-033","","",""
"","","",""
"173821-4","02-12-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.998291E-033","","",""
"","","",""
"173821-5","02-12-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.299912E-023","","",""
"","","",""
"173821-6","02-12-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.495518E-033","","",""
"","","",""
"173821-7","02-12-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.497705E-033","","",""
"","","",""
"173821-8","02-12-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-4.500709E-033","","",""
"","","",""
"173821-9","02-12-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.498047E-033","","",""
"","","",""
"173821-10","02-12-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.049563E-023","","",""
"","","",""
"173821-11","02-12-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-5.014241E-043","","",""
"","","",""
"173821-12","02-12-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.500011E-033","","",""
"","","",""
"175122-1","05-14-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-2.497574E-033","","",""
"","","",""
"175122-2","05-14-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-4.656613E-083","","",""
"","","",""
"175122-3","05-14-2025"
"SCMHVAS-M0200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.005393E-043","","",""
"","","",""
"175122-4","05-14-2025"

View File

@@ -0,0 +1,348 @@
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.997034E-033","","",""
"","","",""
"bri-1","08-23-2024"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.665592E-033","","",""
"","","",""
"164434-28","08-28-2024"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.066744E-023","","",""
"","","",""
"164434-29","08-28-2024"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.99847E-033","","",""
"","","",""
"164434-30","08-28-2024"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 2.667214E-033","","",""
"","","",""
"164434-31","08-28-2024"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.666951E-033","","",""
"","","",""
"164434-32","08-28-2024"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.066665E-023","","",""
"","","",""
"175815-1","07-08-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.232908E-023","","",""
"","","",""
"175815-2","07-08-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.632855E-023","","",""
"","","",""
"175815-3","07-08-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.330595E-033","","",""
"","","",""
"175815-4","07-08-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.667478E-033","","",""
"","","",""
"175815-5","07-08-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.066695E-023","","",""
"","","",""
"175815-6","07-08-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS .01499823","","",""
"","","",""
"175815-7","07-08-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.299847E-023","","",""
"","","",""
"175815-8","07-08-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 8.32912E-033","","",""
"","","",""
"177428-1","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.328026E-033","","",""
"","","",""
"177428-2","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.133021E-023","","",""
"","","",""
"177428-3","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.328026E-033","","",""
"","","",""
"177428-4","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.301771E-043","","",""
"","","",""
"177428-5","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.099533E-023","","",""
"","","",""
"177428-6","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.066355E-023","","",""
"","","",""
"177428-7","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.299507E-023","","",""
"","","",""
"177428-8","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 8.663815E-033","","",""
"","","",""
"177428-9","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 8.995598E-033","","",""
"","","",""
"177428-10","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.664078E-033","","",""
"","","",""
"177428-11","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.665436E-033","","",""
"","","",""
"177428-12","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.330742E-033","","",""
"","","",""
"177428-13","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 2.899593E-023","","",""
"","","",""
"177428-14","11-11-2025"
"SCMHVAS-M0300 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 8.995598E-033","","",""
"","","",""
"177428-15","11-11-2025"

View File

@@ -0,0 +1,60 @@
"SCMHVAS-M0400 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.254585E-033","","",""
"","","",""
"bri-1","08-23-2024"
"SCMHVAS-M0400 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.524978E-023","","",""
"","","",""
"168630-9","08-28-2024"
"SCMHVAS-M0400 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.999184E-033","","",""
"","","",""
"168630-10","08-28-2024"
"SCMHVAS-M0400 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.746245E-033","","",""
"","","",""
"168630-11","08-28-2024"
"SCMHVAS-M0400 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.999713E-033","","",""
"","","",""
"168630-12","08-28-2024"

View File

@@ -0,0 +1,336 @@
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.089708E-063","","",""
"","","",""
"178647-1","02-19-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.501773E-063","","",""
"","","",""
"178647-2","02-19-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.501773E-063","","",""
"","","",""
"178647-3","02-19-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-.00499773","","",""
"","","",""
"178647-4","02-19-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-.00499773","","",""
"","","",""
"178647-5","02-19-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-.00499773","","",""
"","","",""
"178647-6","02-19-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.089708E-063","","",""
"","","",""
"178647-7","02-19-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.501773E-063","","",""
"","","",""
"178647-8","02-19-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.089708E-063","","",""
"","","",""
"178647-9","02-19-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.089708E-063","","",""
"","","",""
"178647-10","02-19-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.140901E-063","","",""
"","","",""
"178647-11","02-19-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.140901E-063","","",""
"","","",""
"178647-12","02-19-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.001798E-033","","",""
"","","",""
"179380-1","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.544854E-063","","",""
"","","",""
"179380-2","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.544854E-063","","",""
"","","",""
"179380-3","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-4.987344E-033","","",""
"","","",""
"179380-4","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.909272E-063","","",""
"","","",""
"179380-5","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.544854E-063","","",""
"","","",""
"179380-6","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-4.987344E-033","","",""
"","","",""
"179380-7","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-4.987344E-033","","",""
"","","",""
"179380-8","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.909272E-063","","",""
"","","",""
"179380-9","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.544854E-063","","",""
"","","",""
"179380-10","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.544854E-063","","",""
"","","",""
"179380-11","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.544854E-063","","",""
"","","",""
"179380-12","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.544854E-063","","",""
"","","",""
"179380-13","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.001798E-033","","",""
"","","",""
"179380-14","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.544854E-063","","",""
"","","",""
"179380-15","04-03-2026"
"SCMHVAS-MPT "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-9.994134E-033","","",""
"","","",""
"179380-16","04-03-2026"

View File

@@ -0,0 +1,540 @@
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.997057E-033","","",""
"","","",""
"-1","05-17-2015"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-3.502657E-033","","",""
"","","",""
"-2","05-17-2015"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.499941E-033","","",""
"","","",""
"-2","05-17-2015"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.005363E-033","","",""
"","","",""
"-1","05-17-2015"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.049749E-023","","",""
"","","",""
"160957-1","11-28-2022"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-4.006503E-033","","",""
"","","",""
"160957-2","11-28-2022"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-6.003258E-033","","",""
"","","",""
"160957-3","11-28-2022"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-7.505388E-033","","",""
"","","",""
"160957-4","11-28-2022"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-3.505917E-033","","",""
"","","",""
"160957-5","11-28-2022"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-5.502672E-033","","",""
"","","",""
"160957-6","11-28-2022"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-3.505917E-033","","",""
"","","",""
"160957-7","11-28-2022"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-6.50403E-033","","",""
"","","",""
"160957-8","11-28-2022"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.491249E-033","","",""
"","","",""
"162077-1","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-5.509052E-033","","",""
"","","",""
"162077-2","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-8.00658E-033","","",""
"","","",""
"162077-3","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-8.00658E-033","","",""
"","","",""
"162077-4","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-7.005408E-033","","",""
"","","",""
"162077-5","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-6.003864E-033","","",""
"","","",""
"162077-6","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.000333E-023","","",""
"","","",""
"162077-7","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-3.00575E-033","","",""
"","","",""
"162077-8","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-6.504636E-033","","",""
"","","",""
"162077-9","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-3.506522E-033","","",""
"","","",""
"162077-10","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-6.504636E-033","","",""
"","","",""
"162077-11","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.503806E-033","","",""
"","","",""
"162077-12","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-9.002164E-033","","",""
"","","",""
"162077-13","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-2.505164E-033","","",""
"","","",""
"162077-14","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-6.504636E-033","","",""
"","","",""
"162077-15","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-6.504636E-033","","",""
"","","",""
"162077-16","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-5.503278E-033","","",""
"","","",""
"162077-17","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-4.507881E-033","","",""
"","","",""
"162077-18","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-9.002164E-033","","",""
"","","",""
"162077-19","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-4.001521E-033","","",""
"","","",""
"162077-20","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-3.005937E-033","","",""
"","","",""
"162077-21","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-3.506709E-033","","",""
"","","",""
"162077-22","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-5.002692E-033","","",""
"","","",""
"162077-23","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.003221E-033","","",""
"","","",""
"162077-24","02-01-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-5.020294E-043","","",""
"","","",""
"163430-1","04-05-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-4.000729E-033","","",""
"","","",""
"163430-2","04-05-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-7.004803E-033","","",""
"","","",""
"163430-3","04-05-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 2.996856E-033","","",""
"","","",""
"163430-4","04-05-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-9.001559E-033","","",""
"","","",""
"163430-5","04-05-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.257285E-063","","",""
"","","",""
"163430-6","04-05-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.998214E-033","","",""
"","","",""
"163430-7","04-05-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-7.004803E-033","","",""
"","","",""
"163430-8","04-05-2023"
"SCMVAS-M200 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-7.004989E-033","","",""
"","","",""
"163430-7","04-05-2023"

View File

@@ -0,0 +1,192 @@
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.000837E-033","","",""
"","","",""
"150251-1","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-6.800668E-033","","",""
"","","",""
"150251-2","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.200462E-033","","",""
"","","",""
"150251-3","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-7.800536E-033","","",""
"","","",""
"150251-4","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-6.601307E-033","","",""
"","","",""
"150251-5","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-3.600213E-033","","",""
"","","",""
"150251-6","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.398597E-033","","",""
"","","",""
"150251-7","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-6.400491E-033","","",""
"","","",""
"150251-8","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.800133E-033","","",""
"","","",""
"150251-9","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-4.399265E-033","","",""
"","","",""
"150251-10","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-5.400623E-033","","",""
"","","",""
"150251-11","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-6.201129E-033","","",""
"","","",""
"150251-12","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-3.200036E-033","","",""
"","","",""
"150251-13","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-7.200997E-033","","",""
"","","",""
"150251-14","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.800133E-033","","",""
"","","",""
"150251-15","06-08-2021"
"SCMVAS-M500 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.798907E-033","","",""
"","","",""
"150251-16","06-08-2021"

View File

@@ -0,0 +1,348 @@
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.965756E-033","","",""
"","","",""
"145728-1","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.253222E-023","","",""
"","","",""
"145728-2","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.36673E-033","","",""
"","","",""
"145728-3","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.566802E-033","","",""
"","","",""
"145728-4","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.798674E-033","","",""
"","","",""
"145728-5","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.999575E-033","","",""
"","","",""
"145728-6","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.009941E-023","","",""
"","","",""
"145728-7","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.998481E-033","","",""
"","","",""
"145728-8","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS .00633343","","",""
"","","",""
"145787-1","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.698836E-033","","",""
"","","",""
"145787-2","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 8.965889E-033","","",""
"","","",""
"145787-3","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.666244E-033","","",""
"","","",""
"145787-4","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.499557E-033","","",""
"","","",""
"145787-5","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.129896E-023","","",""
"","","",""
"145787-6","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.599262E-033","","",""
"","","",""
"145787-7","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS .00783243","","",""
"","","",""
"145787-8","09-30-2020"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 8.230843E-033","","",""
"","","",""
"162080-1","01-25-2023"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.699457E-033","","",""
"","","",""
"162080-2","01-25-2023"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.931858E-033","","",""
"","","",""
"162080-3","01-25-2023"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 8.131005E-033","","",""
"","","",""
"162080-4","01-25-2023"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 8.131005E-033","","",""
"","","",""
"162080-5","01-25-2023"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.731785E-033","","",""
"","","",""
"162080-6","01-25-2023"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.531978E-033","","",""
"","","",""
"162080-7","01-25-2023"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 3.630854E-033","","",""
"","","",""
"162080-8","01-25-2023"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.631118E-033","","",""
"","","",""
"162080-9","01-25-2023"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.498556E-033","","",""
"","","",""
"162080-10","01-25-2023"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 8.432008E-033","","",""
"","","",""
"162080-11","01-25-2023"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 8.765247E-033","","",""
"","","",""
"162080-12","01-25-2023"
"SCMVAS-M600 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 5.264964E-033","","",""
"","","",""
"162080-12","01-25-2023"

View File

@@ -0,0 +1,276 @@
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-8.617003E-033","","",""
"","","",""
"95603-1","03-07-2014"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 6.376649E-033","","",""
"","","",""
"147728-1","01-21-2021"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.769826E-033","","",""
"","","",""
"147728-2","01-21-2021"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-3.423844E-033","","",""
"","","",""
"147728-3","01-21-2021"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.176841E-033","","",""
"","","",""
"147728-4","01-21-2021"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 7.077003E-033","","",""
"","","",""
"147728-5","01-21-2021"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.023267E-033","","",""
"","","",""
"147728-6","01-21-2021"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-5.523418E-033","","",""
"","","",""
"147728-7","01-21-2021"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 9.131152E-033","","",""
"","","",""
"147728-8","01-21-2021"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-6.269407E-033","","",""
"","","",""
"163321-1","04-04-2023"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-8.769822E-033","","",""
"","","",""
"163321-2","04-04-2023"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 2.277095E-033","","",""
"","","",""
"163321-3","04-04-2023"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 1.777906E-033","","",""
"","","",""
"163321-4","04-04-2023"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-4.422467E-033","","",""
"","","",""
"163321-5","04-04-2023"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 4.638289E-033","","",""
"","","",""
"163321-6","04-04-2023"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.922052E-033","","",""
"","","",""
"163321-7","04-04-2023"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-8.977029E-033","","",""
"","","",""
"163321-8","04-04-2023"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-1.103203E-023","","",""
"","","",""
"170355-1","05-23-2024"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-6.854254E-033","","",""
"","","",""
"170355-2","05-23-2024"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-5.246372E-033","","",""
"","","",""
"170351-1","05-23-2024"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-5.738064E-033","","",""
"","","",""
"170351-2","05-23-2024"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS-4.691281E-033","","",""
"","","",""
"170351-3","05-23-2024"
"SCMVAS-M650 "
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0,0,0,0,""
0
"","","",""
"","","",""
"PASS 8.353917E-033","","",""
"","","",""
"170355-3","05-23-2024"

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 10/04/2023
Model: SCMHVAS-M0200
SN: 166590-1
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.007% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 10/04/2023
Model: SCMHVAS-M0200
SN: 166590-2
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.007% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 10/04/2023
Model: SCMHVAS-M0200
SN: 166590-3
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.005% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 10/04/2023
Model: SCMHVAS-M0200
SN: 166590-4
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.006% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 10/04/2023
Model: SCMHVAS-M0700
SN: 166592-1
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.005% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 10/04/2023
Model: SCMHVAS-M0700
SN: 166592-2
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.009% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 10/04/2023
Model: SCMHVAS-M0700
SN: 166592-3
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.003% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 10/04/2023
Model: SCMHVAS-M0700
SN: 166592-4
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.008% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 10/04/2023
Model: SCMHVAS-M1000
SN: 166593-1
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.009% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,34 @@
Dataforth Corporation Phone number: (520) 741-1404
3331 E. Hemisphere Loop Fax: (520) 741-0762
Tucson, AZ 85706 USA Email: info@dataforth.com
TEST DATA SHEET
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date: 10/04/2023
Model: SCMHVAS-M1000
SN: 166593-2
FINAL TEST RESULTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter Measured Value Specification Status
================ ============== ============= ======
Accuracy 0.005% +/- 0.03% PASS
_______________________________________________________________________
Check List
Module Appearance: __X__ Mounting Screw: __X__
Pins Straight: __X__ Module Header: __X__
It is hereby certified that the above product is in conformance with
all requirements to the extent specified. This product is not
authorized or warranted for use in life support devices and/or systems.
* NIST traceable calibration certificates support Measured Value data.
Calibration services are available through ANSI/NCSL Z540-1 and
ISO Guide 25 Certified Metrology Labs.

View File

@@ -0,0 +1,731 @@
'Database Modification Program for DSCA33
'DIN rail signal conditioning modules
'
'AUTHOR: John Lehman
'DATE: 08/30/99
'
' REVISION RECORD
'DATE APPR DESCRIPTION
'---- ---- -----------
'08/30/99 JL Create, start from DBDSC4.BAS
'10/30/06 JL Added suggested P.S. voltage levels of 19-29V.
DECLARE FUNCTION CHANGE (SPEC!)
DECLARE FUNCTION CHANGE2 (SPEC!, SCALE!)
DECLARE FUNCTION MENU1% ()
DECLARE FUNCTION MENU3% () 'Gets the module family
DECLARE FUNCTION REPEAT$ ()
DECLARE FUNCTION STRINGVAL% (A$)
DECLARE SUB MODDATA (SEL%)
DECLARE SUB GETSPECS (SEL%)
DECLARE SUB ENTDATA ()
DECLARE SUB SAVEDATA (SEL%)
DECLARE SUB SORTALL ()
DECLARE SUB SORTDB (ENDFLAG%)
'Database Record defintion for the specifications
TYPE DBASE
MODNAME AS STRING * 13 'DSCA33-XXXX or SCM5B33-XXXX
INTYPE AS STRING * 3 ''V' OR 'A'
MININ AS SINGLE 'Minus F.S. input (V or I)
MAXIN AS SINGLE 'Plus F.S. input (V or I)
OUTSIGTYPE AS STRING * 7 ''VOLTAGE' or 'CURRENT'
MINOUT AS SINGLE 'Minus F.S. output (V or I)
MAXOUT AS SINGLE 'Plus F.S. output (V or I)
WAVESHPCAL AS STRING * 8 ''SINE', 'SQUARE', 'TRIANGLE', 'ARBxxxxx'
FINCAL AS SINGLE 'Calibration Frequency input (Hz)
FINMIN AS SINGLE 'Minimum Std. Frequency input (Hz)
FINMAX AS SINGLE 'Highest Std. Frequency input (Hz)
FINEXTMIN AS SINGLE 'Minimum Extd. Frequency input (Hz)
FINEXTMAX AS SINGLE 'Highest Extd. Frequency input (Hz)
INPROTECT AS SINGLE 'Rated input Protection (V or I)
IOUTLIM AS SINGLE 'Current output limit (mA)
VOUTLIM AS SINGLE 'Voltage output limit (V)
OUTRES AS SINGLE 'Output resistance
OUTNOISE AS SINGLE 'Output noise/ripple 100kHz BW (rms)
OSCALIN AS SINGLE 'Module input for offset calibration
GNCALIN AS SINGLE 'Module input for gain calibration
OSCALPT AS SINGLE 'Offset cal point (%span)
GNCALPT AS SINGLE 'Gain cal point (%span)
CALTOL AS SINGLE 'Calibration tolerance (%span)
ADJ AS SINGLE 'User Adjustability Range (%span)
LINEAR AS SINGLE 'Linearity (% span) at sine input and FINCAL
ACCSINCAL AS SINGLE 'Sine accuracy (%span) at CALFIN
ACCSINSTD AS SINGLE 'Sine accuracy (%span) from FINMIN to FIMMAX
ACCSINEXT AS SINGLE 'Sine accuracy (%span) from FINEXTMIN to FINEXTMAX
ACCCF12 AS SINGLE 'C.F. = 1 TO 2 accuracy (%span) at FINCAL
ACCCF23 AS SINGLE 'C.F. = 2 TO 3 accuracy (%span) at FINCAL
ACCCF34 AS SINGLE 'C.F. = 3 TO 4 accuracy (%span) at FINCAL
ACCCF45 AS SINGLE 'C.F. = 4 TO 5 accuracy (%span) at FINCAL
CMR AS SINGLE 'Common Mode Rejection (dB)
STEPTIME AS SINGLE 'Test time for step response
STEPPERC AS SINGLE '% F.S. @ STEPTIME
STEPTOL AS SINGLE 'STEPPERC measurement tolerance
LOOPVMIN AS SINGLE 'Output Loop Voltage min (V)
LOOPVNOM AS SINGLE 'Output Loop Voltage nom (V)
LOOPVMAX AS SINGLE 'Output Loop Voltage max (V)
MAXLOADR AS SINGLE 'Maximum load resistance (ohms)
MINVS AS SINGLE 'Lowest supply voltage (V)
NOMVS AS SINGLE 'Nominal supply voltage (V)
MAXVS AS SINGLE 'Highest supply voltage (V)
ISMIN AS SINGLE 'Minimum suuply current (mA)
ISMAX AS SINGLE 'Supply current, full load (mA)
PSS AS SINGLE 'Power supply sensitivity (ppm/% or %/% Delta Vs)
END TYPE
TYPE DBASE2
RECNUM AS INTEGER
MODNAME AS STRING * 13
END TYPE
'define common variables
COMMON SHARED /SAMPLE/ SPECS AS DBASE, SORTDATA1 AS DBASE2, SORTDATA2 AS DBASE
DO
SELECT CASE MENU1%
CASE 1
CALL ENTDATA
IF LEFT$(SPECS.MODNAME, 4) <> " " THEN
CALL SAVEDATA(0)
END IF
CASE 2
CALL GETSPECS(SEL%)
IF LEFT$(SPECS.MODNAME, 4) <> "EXIT" THEN
CALL MODDATA(SEL%)
CALL SAVEDATA(0)
END IF
CASE 3
CALL GETSPECS(SEL%)
IF LEFT$(SPECS.MODNAME, 4) <> "EXIT" THEN
CALL MODDATA(SEL%)
CALL SAVEDATA(SEL%)
END IF
CASE 4
CALL SORTALL
CASE 5
END
END SELECT
LOOP WHILE REPEAT$ <> "N"
FUNCTION CHANGE (SPEC!)
Y% = CSRLIN
X% = POS(0)
LOCATE Y%, X% + 1
INPUT A$
IF A$ <> "" THEN
CHANGE = VAL(A$)
ELSE
CHANGE = SPEC!
END IF
END FUNCTION
FUNCTION CHANGE2 (SPEC!, SCALE!)
Y% = CSRLIN
X% = POS(0)
LOCATE Y%, X% + 1
INPUT A$
IF A$ <> "" THEN
CHANGE2 = VAL(A$) / SCALE!
ELSE
CHANGE2 = SPEC!
END IF
END FUNCTION
SUB ENTDATA
'for specs which are not input for all models, default value is zero.
CLS
INPUT "SCM5B, SCM8B or DSCA Model Number (SCM5Bxx-xx, SCM8Bxx-xx or DSCAxx-xx, blank to exit) "; MN$
IF LEFT$(MN$, 1) = " " THEN
EXIT SUB
ELSE 'Check if model already exists
OPEN "T:\ENGR\ATE\ATE\HVU\HVDATA\HVIN.DAT" FOR RANDOM AS #1 LEN = LEN(SPECS)
NR% = LOF(1) / LEN(SPECS)
FOR N = 1 TO NR%
GET #1, N, SPECS
IF SPECS.MODNAME = MN$ THEN
BEEP
PRINT "This model already exists in the database."
PRINT "Press any key to continue."
DO
LOOP WHILE INKEY$ = ""
CLOSE #1
EXIT SUB
END IF
NEXT N
CLOSE #1
SPECS.MODNAME = UCASE$(MN$)
END IF
PRINT "Valid Sensor Types:"
PRINT TAB(5); "V (millivolt or volt source)"
' PRINT TAB(5); "A (milliamp or amp source)"
DO
INPUT "Sensor Type "; SPECS.INTYPE
LOOP WHILE LEFT$(UCASE$(SPECS.INTYPE), 1) <> "V" AND LEFT$(UCASE$(SPECS.INTYPE), 1) <> "A"
INPUT "Minus F.S. Input (V, A) "; SPECS.MININ
INPUT "Plus F.S. Input (V, A) "; SPECS.MAXIN
DO
INPUT "Output signal type ('VOLTAGE' or 'CURRENT')"; SPECS.OUTSIGTYPE
SPECS.OUTSIGTYPE$ = UCASE$(SPECS.OUTSIGTYPE)
LOOP WHILE SPECS.OUTSIGTYPE <> "VOLTAGE" AND SPECS.OUTSIGTYPE <> "CURRENT"
IF SPECS.OUTSIGTYPE = "CURRENT" THEN
UNIT$ = "mA"
SCALE! = 1000!
ELSE
UNIT$ = "V"
SCALE! = 1!
END IF
PRINT "Minus F.S. Output ("; UNIT$; ") ";
INPUT SPECS.MINOUT
SPECS.MINOUT = SPECS.MINOUT / SCALE! 'Convert to V or A
PRINT "Plus F.S. Output ("; UNIT$; ") ";
INPUT SPECS.MAXOUT
SPECS.MAXOUT = SPECS.MAXOUT / SCALE! 'Convert to V or A
' DO
' INPUT "Calibration Signal Waveshape (SINE, SQUARE, TRIANGLE, RAMP) = "; SPECS.WAVESHPCAL
' SPECS.WAVESHPCAL = UCASE$(SPECS.WAVESHPCAL)
'LOOP WHILE SPECS.WAVESHPCAL <> "SINE " AND SPECS.WAVESHPCAL <> "SQUARE " AND SPECS.WAVESHPCAL <> "TRIANGLE" AND SPECS.WAVESHPCAL <> "RAMP "
' INPUT "Calibration Signal Frequency (Hz, 60Hz standard) "; SPECS.FINCAL
' INPUT "Mininum Standard Input Frequency (Hz) "; SPECS.FINMIN
' INPUT "Maximum Standard Input Frequency (Hz) "; SPECS.FINMAX
' INPUT "Mininum Extended Input Frequency (Hz) "; SPECS.FINEXTMIN
' INPUT "Maximum Extended Input Frequency (Hz) "; SPECS.FINEXTMAX
'INPUT "Input Protection Rating, Max (V, A) "; SPECS.INPROTECT
'IF SPECS.OUTSIGTYPE = "CURRENT" THEN
' INPUT "Current Output Limit (mA) "; SPECS.IOUTLIM
'ELSE
' INPUT "Voltage Output Limit (V) "; SPECS.VOUTLIM
'END IF
'IF LEFT$(SPECS.MODNAME, 5) = "SCM5B" THEN
' INPUT "Output Resistance (Ohms) "; SPECS.OUTRES
'END IF
INPUT "Output noise/ripple (% span rms) "; SPECS.OUTNOISE
INPUT "Input for OFFSET calibration (V, A) "; SPECS.OSCALIN
INPUT "Input for GAIN calibration (V, A) "; SPECS.GNCALIN
INPUT "Offset Calibration Point (+/- % of span) "; SPECS.OSCALPT
INPUT "Gain Calibration Point (+/- % of span) "; SPECS.GNCALPT
INPUT "Offset and Gain Calibration Tolerance (+/- % of Span) "; SPECS.CALTOL
INPUT "Offset/Gain User Adjustability (+/- % of Span) "; SPECS.ADJ
INPUT "Nonlinearity (+/- % of span) "; SPECS.LINEAR
'PRINT USING "Accuracy z (+/- % of span) "; SPECS.FINCAL
INPUT "Accuracy (+/- % of span) "; SPECS.ACCSINCAL
' PRINT USING "Accuracy w/ Sine Input @ #####Hz to #####Hz (+/- % of span) "; SPECS.FINMIN; SPECS.FINMAX
' INPUT SPECS.ACCSINSTD
' PRINT USING "Accuracy w/ Sine Input @ #####Hz to #####Hz (+/- % of span) "; SPECS.FINEXTMIN; SPECS.FINEXTMAX
' INPUT SPECS.ACCSINEXT
' PRINT USING "Accuracy w/ Crest Factor 1 to 2 @ #####Hz (+/- % of span) "; SPECS.FINCAL
'INPUT SPECS.ACCCF12
'PRINT USING "Accuracy w/ Crest Factor 2 to 3 @ #####Hz (+/- % of span) "; SPECS.FINCAL
' INPUT SPECS.ACCCF23
' PRINT USING "Accuracy w/ Crest Factor 3 to 4 @ #####Hz (+/- % of span) "; SPECS.FINCAL
' INPUT SPECS.ACCCF34
' PRINT USING "Accuracy w/ Crest Factor 4 to 5 @ #####Hz (+/- % of span) "; SPECS.FINCAL
' INPUT SPECS.ACCCF45
' INPUT "Common Mode Rejection (dB) "; SPECS.CMR
INPUT "Step Response Test Delay (s, 0.1 typ.)"; SPECS.STEPTIME
PRINT "Nominal Response at #.### s (% span)"; SPECS.STEPTIME;
' INPUT SPECS.STEPPERC
' INPUT "Step Response Tolerance (%) "; SPECS.STEPTOL
IF SPECS.OUTSIGTYPE = "CURRENT" THEN
INPUT "Maximum Load Resistance (Ohms) "; SPECS.MAXLOADR
IF LEFT$(SPECS.MODNAME, 5) = "SCM5B" THEN
INPUT "Minimum Output Loop Voltage (V) "; SPECS.LOOPVMIN
INPUT "Nominal Output Loop Voltage (V) "; SPECS.LOOPVNOM
INPUT "Maximum Output Loop Voltage (V) "; SPECS.LOOPVMAX
END IF
END IF
INPUT "Minimum Supply Voltage (V) [5B/8B- 4.75V, DSCA- 19V]"; SPECS.MINVS
INPUT "Nominal Supply Voltage (V) [5B/8B- 5V, DSCA- 24V]"; SPECS.NOMVS
INPUT "Maximum Supply Voltage (V) [5B/8B- 5.25V, DSCA- 29V]"; SPECS.MAXVS
INPUT "Minimum Supply Current (mA) "; SPECS.ISMIN
INPUT "Maximum Supply Current (mA) "; SPECS.ISMAX
IF LEFT$(SPECS.MODNAME, 5) = "SCM5B" THEN
UNIT$ = "ppm / %"
ELSEIF LEFT$(SPECS.MODNAME, 2) = "8B" THEN
UNIT$ = "ppm / %"
ELSE
UNIT$ = "% / %"
END IF
PRINT "Power Supply Sensitivity ("; UNIT$; ") ";
INPUT SPECS.PSS
END SUB
SUB GETSPECS (SEL%)
DIM POINTER%(1000)
CALL SORTDB(ENDFLAG%)
IF ENDFLAG% = 1 THEN
SPECS.MODNAME = "EXIT"
EXIT SUB
END IF
CLS
LOCATE , 30
PRINT "Model Selection Menu"
PRINT TAB(30); "--------------------"
PRINT
YINIT% = CSRLIN 'Initialize starting rows
OPEN "T:\ENGR\ATE\HVU\HVDATA\HVSORT.DAT" FOR RANDOM AS #2 LEN = LEN(SORTDATA1)
DO
I% = I% + 1
GET #2, I%, SORTDATA1
IF SORTDATA1.RECNUM <> -1 THEN NUMRECORD! = NUMRECORD! + 1
LOOP WHILE SORTDATA1.RECNUM <> -1
SCRNCTR% = 40 'display center
NUMCHAR% = 19 '13 char + 4 char for # + 2 spaces
NUMLINES! = 19 '# of lines for model display below header
NUMCOLUMNS! = 1 + INT(NUMRECORD! / NUMLINES!)
'Model # length = 20 characters max
'-> 3 columns max per screen
'3 columns x 26 char = 78 spaces
I% = 1
FOR C% = 0 TO NUMCOLUMNS! - 1
Y% = YINIT%
TB% = SCRNCTR% - NUMCHAR% / 2 * NUMCOLUMNS! + NUMCHAR% * C%
DO
GET #2, I%, SORTDATA1
IF SORTDATA1.RECNUM <> -1 THEN
POINTER%(I%) = SORTDATA1.RECNUM
LOCATE Y%, TB%
PRINT USING "##.) &"; I%; SORTDATA1.MODNAME
I% = I% + 1
Y% = Y% + 1
END IF
LOOP WHILE SORTDATA1.RECNUM <> -1 AND Y% - YINIT% < NUMLINES!
NEXT C%
LOCATE Y%, TB%
PRINT USING "##.) Exit"; I%
CLOSE #2
DO
LOCATE 23, SCRNCTR% - NUMCHAR% / 2 * NUMCOLUMNS!
PRINT "Enter Selection ";
INPUT SEL%
LOOP WHILE SEL% < 1 OR SEL% > I%
IF SEL% = I% THEN
SPECS.MODNAME = "EXIT"
ELSE
OPEN "T:\ENGR\ATE\HVU\HVDATA\HVIN.DAT" FOR RANDOM AS #1 LEN = LEN(SPECS)
GET #1, POINTER%(SEL%), SPECS
CLOSE #1
END IF
SEL% = POINTER%(SEL%) 'pass back to main code for use in SAVEDATA
END SUB
FUNCTION MENU1%
CLS
LOCATE 5
PRINT TAB(18); "SCM5Bxx, SCM8Bxx & DSCAxx DATABASE MODIFICATION PROGRAM"
PRINT
PRINT TAB(17); "1.) Add New Module, Clean Start"
PRINT TAB(17); "2.) Add New Module, Start With Existing Module"
PRINT TAB(17); "3.) Modify Existing Module"
PRINT TAB(17); "4.) Sort Database"
PRINT TAB(17); "5.) Exit Program"
PRINT
PRINT TAB(17); "Enter your selection";
DO
A$ = INKEY$
LOOP WHILE VAL(A$) < 1 OR VAL(A$) > 5
MENU1% = VAL(A$)
END FUNCTION
FUNCTION MENU3%
CLS
LOCATE 3, 26
PRINT "Module Family Selection Menu"
PRINT TAB(26); "---------------------------"
PRINT
PRINT TAB(34); "1.) SCM5Bxx-xx"
PRINT TAB(34); "2.) DSCAxx-xx"
PRINT TAB(34); "3.) 8Bxx-xx"
PRINT TAB(34); "4.) Exit"
DO
I$ = INKEY$
LOOP WHILE VAL(I$) < 1 OR VAL(I$) > 4
MENU3% = VAL(I$)
END FUNCTION
SUB MODDATA (SEL%)
CLS
PRINT "Model Number: "; SPECS.MODNAME;
INPUT MN$
IF MN$ <> "" THEN
'Check if model already exists
OPEN "T:\ENGR\ATE\HVU\HVDATA\HVIN.DAT" FOR RANDOM AS #1 LEN = LEN(SPECS)
NR% = LOF(1) / LEN(SPECS)
FOR N% = 1 TO NR%
GET #1, N%, SPECS
IF SPECS.MODNAME = MN$ THEN
BEEP
PRINT "This model already exists in the database."
PRINT "Press any key to continue."
DO
LOOP WHILE INKEY$ = ""
CLOSE #1
EXIT SUB
END IF
NEXT
GET #1, SEL%, SPECS
SPECS.MODNAME = MN$
CLOSE #1
END IF
PRINT "Sensor Type: "; SPECS.INTYPE;
INPUT A$
IF A$ <> "" THEN
SPECS.INTYPE = A$
END IF
PRINT USING "Minus F.S. Input = +###.### (V, A)"; SPECS.MININ;
SPECS.MININ = CHANGE(SPECS.MININ)
PRINT USING "Plus F.S. Input = +###.### (V, A)"; SPECS.MAXIN;
SPECS.MAXIN = CHANGE(SPECS.MAXIN)
DO
PRINT "Output signal type = "; SPECS.OUTSIGTYPE;
INPUT A$
IF A$ <> "" THEN SPECS.OUTSIGTYPE = UCASE$(A$)
LOOP WHILE SPECS.OUTSIGTYPE <> "VOLTAGE" AND SPECS.OUTSIGTYPE <> "CURRENT"
IF SPECS.OUTSIGTYPE = "CURRENT" THEN
UNIT$ = "mA"
SCALE! = 1000!
ELSE
UNIT$ = "V"
SCALE! = 1!
END IF
PRINT USING "Minus F.S. Output = +#####.## &"; SPECS.MINOUT * SCALE!; UNIT$;
SPECS.MINOUT = CHANGE2(SPECS.MINOUT, SCALE!)
PRINT USING "Plus F.S. Output = +#####.## &"; SPECS.MAXOUT * SCALE!; UNIT$;
SPECS.MAXOUT = CHANGE2(SPECS.MAXOUT, SCALE!)
' DO
' PRINT "Calibration Signal Waveshape (SINE, SQUARE, TRIANGLE, RAMP) = "; SPECS.WAVESHPCAL
' INPUT A$
' IF A$ <> "" THEN SPECS.WAVESHPCAL = UCASE$(A$)
'LOOP WHILE SPECS.WAVESHPCAL <> "SINE " AND SPECS.WAVESHPCAL <> "SQUARE " AND SPECS.WAVESHPCAL <> "TRIANGLE" AND SPECS.WAVESHPCAL <> "RAMP "
' PRINT USING "Calibration Signal Frequency = ###### Hz"; SPECS.FINCAL;
' SPECS.FINCAL = CHANGE(SPECS.FINCAL)
' PRINT USING "Mininum Standard Input Frequency = ##### Hz"; SPECS.FINMIN;
' SPECS.FINMIN = CHANGE(SPECS.FINMIN)
' PRINT USING "Maximum Standard Input Frequency = ##### Hz"; SPECS.FINMAX;
' SPECS.FINMAX = CHANGE(SPECS.FINMAX)
' PRINT USING "Mininum Extended Input Frequency = ##### Hz"; SPECS.FINEXTMIN;
' SPECS.FINEXTMIN = CHANGE(SPECS.FINEXTMIN)
' PRINT USING "Maximum Extended Input Frequency = ###### Hz"; SPECS.FINEXTMAX;
' SPECS.FINEXTMAX = CHANGE(SPECS.FINEXTMAX)
' PRINT USING "Input Protection Rating, Max = #### (V or A)"; SPECS.INPROTECT;
' SPECS.INPROTECT = CHANGE(SPECS.INPROTECT)
' IF SPECS.OUTSIGTYPE = "CURRENT" THEN
' PRINT USING "Current Output Limit = ##.# mA"; SPECS.IOUTLIM;
' SPECS.IOUTLIM = CHANGE(SPECS.IOUTLIM)
'ELSE
' PRINT USING "Voltage Output Limit = ##.# V"; SPECS.VOUTLIM;
' SPECS.VOUTLIM = CHANGE(SPECS.VOUTLIM)
'END IF
'IF LEFT$(SPECS.MODNAME, 5) = "SCM5B" THEN
' PRINT USING "Output Resistance = ### Ohms"; SPECS.OUTRES;
' SPECS.OUTRES = CHANGE(SPECS.OUTRES)
'END IF
PRINT USING "Output Noise = #.### % span rms"; SPECS.OUTNOISE;
SPECS.OUTNOISE = CHANGE(SPECS.OUTNOISE)
PRINT USING "Input for OFFSET calibration = +###.### (V, A)"; SPECS.OSCALIN;
SPECS.OSCALIN = CHANGE(SPECS.OSCALIN)
PRINT USING "Input for GAIN calibration = +###.### (V, A)"; SPECS.GNCALIN;
SPECS.GNCALIN = CHANGE(SPECS.GNCALIN)
PRINT
PRINT "Offset and gain may be calibrated at a point other than -f.s. out"
PRINT "or +f.s. out."
PRINT USING "Offset Calibration Point = +#.### % of span"; SPECS.OSCALPT;
SPECS.OSCALPT = CHANGE(SPECS.OSCALPT)
PRINT USING "Gain Calibration Point = +#.### % of span"; SPECS.GNCALPT;
SPECS.GNCALPT = CHANGE(SPECS.GNCALPT)
PRINT USING "Offset and Gain Calibration Tolerance = +/- #.### % of span"; SPECS.CALTOL;
SPECS.CALTOL = CHANGE(SPECS.CALTOL)
PRINT USING "Offset/Gain User Adjustability = +/- ##.# % of span"; SPECS.ADJ;
SPECS.ADJ = CHANGE(SPECS.ADJ)
PRINT USING "Nonlinearity = +/- #.### % of span"; SPECS.LINEAR;
SPECS.LINEAR = CHANGE(SPECS.LINEAR)
PRINT USING "Accuracy = +/- #.### % of span"; SPECS.ACCSINCAL;
SPECS.ACCSINCAL = CHANGE(SPECS.ACCSINCAL)
' PRINT USING "Addt'l Error w/ Sine Input @ ##### Hz to ##### Hz = +/- #.### % of span"; SPECS.FINMIN; SPECS.FINMAX; SPECS.ACCSINSTD;
' SPECS.ACCSINSTD = CHANGE(SPECS.ACCSINSTD)
' PRINT USING "Addt'l Error w/ Sine Input @ ##### Hz to ##### Hz = +/- #.### % of span"; SPECS.FINEXTMIN; SPECS.FINEXTMAX; SPECS.ACCSINEXT;
' SPECS.ACCSINEXT = CHANGE(SPECS.ACCSINEXT)
' PRINT USING "Addt'l Error w/ Crest Factor 1 to 2 @ ##### Hz = +/- #.### % of span"; SPECS.FINCAL; SPECS.ACCCF12;
' SPECS.ACCCF12 = CHANGE(SPECS.ACCCF12)
' PRINT USING "Addt'l Error w/ Crest Factor 2 to 3 @ ##### Hz = +/- #.### % of span"; SPECS.FINCAL; SPECS.ACCCF23;
' SPECS.ACCCF23 = CHANGE(SPECS.ACCCF23)
' PRINT USING "Addt'l Error w/ Crest Factor 3 to 4 @ ##### Hz = +/- #.### % of span"; SPECS.FINCAL; SPECS.ACCCF34;
' SPECS.ACCCF34 = CHANGE(SPECS.ACCCF34)
' PRINT USING "Addt'l Error w/ Crest Factor 4 to 5 @ ##### Hz = +/- #.### % of span"; SPECS.FINCAL; SPECS.ACCCF45;
' SPECS.ACCCF45 = CHANGE(SPECS.ACCCF45)
' PRINT USING "Common Mode Rejection = ### dB"; SPECS.CMR;
' SPECS.CMR = CHANGE(SPECS.CMR)
PRINT USING "Step Response Test Delay = #.###### s"; SPECS.STEPTIME;
SPECS.STEPTIME = CHANGE(SPECS.STEPTIME)
' PRINT USING "Nominal Response at #.### s = ###.# % span"; SPECS.STEPTIME; SPECS.STEPPERC;
'SPECS.STEPPERC = CHANGE(SPECS.STEPPERC)
'PRINT USING "Step Response Tolerance = +/- ##.# % "; SPECS.STEPTOL;
'SPECS.STEPTOL = CHANGE(SPECS.STEPTOL)
IF SPECS.OUTSIGTYPE = "CURRENT" THEN
PRINT USING "Maximum Load Resistance = ##### Ohms"; SPECS.MAXLOADR;
SPECS.MAXLOADR = CHANGE(SPECS.MAXLOADR)
IF LEFT$(SPECS.MODNAME, 5) = "SCM5B" THEN
PRINT USING "Minimum Output Loop Voltage = ##.# V"; SPECS.LOOPVMIN;
SPECS.LOOPVMIN = CHANGE(SPECS.LOOPVMIN)
PRINT USING "Nominal Output Loop Voltage = ##.# V"; SPECS.LOOPVNOM;
SPECS.LOOPVNOM = CHANGE(SPECS.LOOPVNOM)
PRINT USING "Maximum Output Loop Voltage = ##.# V"; SPECS.LOOPVMAX;
SPECS.LOOPVMAX = CHANGE(SPECS.LOOPVMAX)
END IF
END IF
PRINT USING "Minimum Supply Voltage = ##.## V"; SPECS.MINVS;
SPECS.MINVS = CHANGE(SPECS.MINVS)
PRINT USING "Nominal Supply Voltage = ##.## V"; SPECS.NOMVS;
SPECS.MINVS = CHANGE(SPECS.MINVS)
PRINT USING "Maximum Supply Voltage = ##.## V"; SPECS.MAXVS;
SPECS.MAXVS = CHANGE(SPECS.MAXVS)
PRINT USING "Minimum Supply Current = ### mA"; SPECS.ISMIN;
SPECS.ISMIN = CHANGE(SPECS.ISMIN)
PRINT USING "Maximum Supply Current = ### mA"; SPECS.ISMAX;
SPECS.ISMAX = CHANGE(SPECS.ISMAX)
IF LEFT$(SPECS.MODNAME, 5) = "SCM5B" THEN
PRINT USING "Power Supply Sensitivity = #### ppm / %"; SPECS.PSS;
ELSEIF LEFT$(SPECS.MODNAME, 2) = "8B" THEN
PRINT USING "Power Supply Sensitivity = #### ppm / %"; SPECS.PSS;
ELSE
PRINT USING "Power Supply Sensitivity = #.#### % / %"; SPECS.PSS;
END IF
SPECS.PSS = CHANGE(SPECS.PSS)
END SUB
FUNCTION REPEAT$
CLS
LOCATE 10, 10
PRINT "Do you want to enter or modify another module ?";
DO
A$ = INKEY$
LOOP WHILE A$ = ""
REPEAT$ = A$
END FUNCTION
SUB SAVEDATA (SEL%)
CLS
LOCATE 10, 15
PRINT "Saving record in file HVIN.DAT"
T = TIMER
DO
LOOP WHILE (TIMER - T) < 1
OPEN "T:\ENGR\ATE\HVU\HVDATA\HVIN.DAT" FOR RANDOM AS #1 LEN = LEN(SPECS)
IF SEL% = 0 THEN
NR% = LOF(1) / LEN(SPECS)
SEL% = NR% + 1
END IF
PUT #1, SEL%, SPECS
CLOSE #1
END SUB
SUB SORTALL
'This sub does a bubble sort of the model numbers in the database
'records FOR ALL MODELS
CONST FALSE = 0, TRUE = NOT FALSE
OPEN "T:\ENGR\ATE\HVU\HVDATA\HVIN.DAT" FOR RANDOM AS #1 LEN = LEN(SPECS)
NUMRECORD% = LOF(1) / LEN(SPECS)
PRINT
PRINT TAB(10); "Sorting database, please wait."
'Sort according to string value
DO
Swaps% = FALSE
FOR I% = 1 TO NUMRECORD%
GET #1, I%, SPECS
A% = STRINGVAL%(SPECS.MODNAME)
GET #1, I% + 1, SORTDATA2
B% = STRINGVAL%(SORTDATA2.MODNAME)
'Do sort...
IF A% > B% THEN
PUT #1, I%, SORTDATA2
PUT #1, (I% + 1), SPECS
Swaps% = I%
END IF
NEXT I%
LOOP WHILE Swaps%
CLOSE #1
END SUB
SUB SORTDB (ENDFLAG%)
'This sub does a bubble sort of the model numbers in the database
'records FOR A SELECTED MODEL FAMILY
'CONST FALSE = 0, TRUE = NOT FALSE
'Create a file containing just the model numbers of all of the
'records in the database for the model family selected
OPEN "T:\ENGR\ATE\HVU\HVDATA\HVSORT.DAT" FOR RANDOM AS #2 LEN = LEN(SORTDATA1)
OPEN "T:\ENGR\ATE\HVU\HVDATA\HVIN.DAT" FOR RANDOM AS #1 LEN = LEN(SPECS)
NUMRECORD% = LOF(1) / LEN(SPECS)
SELFAM% = MENU3%
IF SELFAM% = 4 THEN
ENDFLAG% = 1
CLOSE #1
CLOSE #2
CLOSE #3
EXIT SUB 'exit selected from MENU3%
END IF
N% = 0
FOR I% = 1 TO NUMRECORD%
GET #1, I%, SPECS
MN$ = SPECS.MODNAME
SORTDATA1.MODNAME = MN$
SORTDATA1.RECNUM = I%
SELECT CASE SELFAM% 'Pick models in selected family
CASE 1
'SPECS.MODNAME = "SCM5Bxx-xxxx "
IF LEFT$(MN$, 3) = "SCM" THEN
PUT #2, , SORTDATA1 'write record # and model number
N% = N% + 1 'found a record
END IF
CASE 2
'SPECS.MODNAME = "DSCAxx-xxxx "
IF LEFT$(MN$, 3) = "DSC" THEN
PUT #2, , SORTDATA1 'write record # and model number
N% = N% + 1 'found a record
END IF
CASE 3
'SPECS.MODNAME = "8Bxx-xxxx "
IF LEFT$(MN$, 2) = "8B" THEN
PUT #2, , SORTDATA1 'write record # and model number
N% = N% + 1 'found a record
END IF
END SELECT
NEXT
SORTDATA1.MODNAME = "99999999-9999" 'always sorted to last value.
SORTDATA1.RECNUM = -1 'flag indicating end of new data (over writes old)
PUT #2, , SORTDATA1
CLOSE #1
CLOSE #2
CLOSE #3
END SUB
FUNCTION STRINGVAL% (A$)
'Calculate and return the value of the characters in A$
'which follow the hyphen
T% = LEN(A$)
DO
L% = L% + 1
LOOP UNTIL MID$(A$, L%, 1) = "-" OR L% = T%
STRINGVAL% = VAL(RIGHT$(A$, T% - L%))
END FUNCTION

View File

@@ -0,0 +1,776 @@
DECLARE SUB INSTALLDUT (NUMDUT%)
'Library of functions used with SCM5B33 and DSCA33 test programs.
'Created from LIBATE.BAS
'Author: John Lehman
'Date: 06/21/99
'
' REVISION RECORD
'
'Date of Change Description
'-------------- -----------
'06/21/99 JL Initial Release
DECLARE SUB CHANGEDN (SN$) 'Allow user to change dash number
DECLARE SUB CONTINUE () 'Waits for a key press
DECLARE SUB GETNEXTSN (TIME2!, SERNO$(), NUMDUT%) 'Allows user the change the SN info for a new group of modules
DECLARE SUB GETSN (SN$) 'Gets DUT serial number from user
DECLARE SUB INPUTSN (SERNO$(), NUMDUT%) 'Gathers the SN infor for the unit in slot 1
DECLARE SUB HS1 () 'GPIB communications handshake
DECLARE SUB pause (TIME!) 'Pause for TIME
DECLARE FUNCTION BESTFIT! (SLOPE!, OFFSET1!, INSIM!(), NUMPTS%, ERROROUT!(), L%)
DECLARE FUNCTION REPEAT2$ (MN$, ELAP2!, TIME2!, SN$)
'** Calculates besfit line and max error
DECLARE FUNCTION KEYBDIN$ () '** Get keyboard input
DECLARE FUNCTION REPEAT$ (MN$, ELAP2!, TIME2!) '** Ask if you would like to repeat test
DECLARE FUNCTION READDVM! (CH%, FUNC$, RANGETXT$, RANGENUM!, RESOLTXT$, RESOLNUM!) '**
DECLARE FUNCTION STRINGVAL% (A$) '**
DECLARE FUNCTION UPSN$ (SN$, NUMDUT%) '** Increments dash# of serial#
'** Function called from main programs.
' Declare in programs which call this function.
'GPIB Communication routines for dataforth GPIB card
DECLARE SUB HS2 ()
DECLARE SUB HS1 ()
DECLARE SUB INIT488 (DEVADDR%)
DECLARE SUB IO488 (DDATA$, DEVADDR%, SEL4%)
'Kepco DPS 125-0.5M control routines via RS-232C
DECLARE SUB INITPS (DPSADDR%, OVERI!, OVERV!) 'initializes supply
DECLARE SUB SETDPS (DPSADDR%, VSUPPLY!) 'Sets Kepco DPS power supply
DECLARE FUNCTION POWERIO$ (DPSADDR%, CMD$) '** Kepco DPS power supply I/O
'HP33120A (function generator) control routines via GPIB
DECLARE SUB FUNGEN33120A (CMD$, VALUE!) 'Write command to func. gen.
'HP34970A (data acquisition/switch unit) control routines via GPIB
DECLARE SUB DVMCONF (CH%, FUNC$, RANGETXT$, RANGENUM!, RESOLTXT$, RESOLNUM!) 'Configure DVM
DECLARE SUB DVMSENS (CH%, FUNC1$, FUNC2$, SETTXT$, SETNUM!) 'Configure DVM
DECLARE SUB SETDAC3 (DACNUM%, VOLTAGE!) 'Set 34907 DACs
DECLARE SUB SETSWITCH (CH%, STATE%) 'Set switch on 34903A 20-ch actuator
DECLARE FUNCTION GETREADING! () '** Send trigger & read dvm
'HP760A meter calibrator routines
DECLARE SUB HP760APANEL (VALUE!, UNITS%) 'Draw a display of instrument panel.
COMMON SHARED PON%, IINSEN1.MEAS!, IOUTSEN1.MEAS!(), IOUTSEN2.MEAS!(), SERNO$(), SN$, PSCOM$, GENOUTMAX!, PCBNO$
COMMON SHARED BADDRS%, DPSADDR%, PSPORT%, DASADDR%, GENADDR%, DIOCH01DATA%, DIOCH02DATA%
CONST MTA% = &H40 'GPIB talk address
CONST MLA% = &H20 'GPIB listen address
'********************************************************************
'Assign constants to Kepco DPS power supply commands:
CONST PSVOLT$ = "STV=" 'Set terminal voltage
CONST SUPPLYON$ = "SOP=ON" 'Set output to ON
CONST ISTAT$ = "RCS" 'Read Current protection status
CONST SETIMAX$ = "SOC=" 'Set overcurent limit...4mA resol.
'********************************************************************
'Assign constants to HP freq. gen. commands
CONST FREQ$ = "FREQ"
'********************************************************************
'Assign constants to HP DAS commands
CONST SETDAC$ = "SOUR:VOLT"
'********************************************************************
'Assign constants to HP DAS configuration
CONST DAC1.CH% = 304 'DAC #1
CONST DAC2.CH% = 305 'DAC #2
KEY(10) ON 'Activates F10 key
ON KEY(10) GOSUB FINISH 'Traps for F10 key Exits if pressed
FINISH: END
FUNCTION BESTFIT! (SLOPE!, OFFSET1!, INSIM!(), NUMPTS%, ERROROUT!(), L%)
'Calculates Max error of data from bestfit line
MTERR! = 0
MIN! = INSIM!(L%, 1)
MAX! = INSIM!(L%, NUMPTS%)
FOR INC% = 1 TO NUMPTS% 'Increments thru test points
BVEC! = INSIM!(L%, INC%) * SLOPE! + OFFSET1! 'Calculates point on best fit line
AVEC! = ERROROUT!(L%, INC%) 'Gets corresponding data point
TERR! = AVEC! - BVEC! 'Calculates difference
IF ABS(TERR!) > ABS(MTERR!) THEN 'Checks if bigger than last Max
MTERR! = TERR! 'Assigns max error
END IF
NEXT
BESTFIT! = MTERR! 'Passes Max error back
END FUNCTION
SUB CHANGEDN (SN$)
N = 1
NWO$ = ""
DO
C$ = MID$(SN$, N, 1)
NWO$ = NWO$ + C$
N = N + 1
LOOP WHILE C$ <> "-"
WO$ = LEFT$(SN$, N)
PRINT "Current serial number is: "; SN$
INPUT "Enter the new dash number (Press 'Enter' for 1) "; DS$
IF DS$ = "" THEN DS$ = "1"
SN$ = NWO$ + DS$
END SUB
SUB DVMCONF (CH%, FUNC$, RANGETXT$, RANGENUM!, RESOLTXT$, RESOLNUM!)
' Inputs:
' CH% = channel to be measured
' FUNC$ = parameter to be measured
' RANGETXT$ = measurement range, specified in text
' If 'N/A', don't include in command. Not needed for FUNC$
' If "", range specified in RANGENUM! or not specified
' RANGENUM! = measurement range, specified numerically
' If 0, range specified in RANGETXT$ or not specified
' RESOLTXT$ = indicate how resolution is specified. 0 = text, 1 = numerically
' If 'N/A', don't include in command. Not needed for FUNC$
' IF "", resolution specified in RESOLNUM! or not specified
' RESOLNUM! = measurement resolution
' 0 = Resolution specified in RESOLTXT$ or not specified
' Outputs: None
' Description - configures HP34970A to take a measurement
'
' Reference;
' MEAS and CONF use the following default instrument settings
' Integration Time 1PLC
' Input Resistane 10Mohm, fixed for all DCV ranges
' AC Filter 20Hz (medium)
' Scan List Redefined when command executed
' Scan Interval Source Immediate
' Scan Count 1 Scan Sweep
' Channel Delay Automatic Delay
IF RANGETXT$ = "N/A" THEN 'String command only.
'No range or resolution data.
DDATA$ = "CONF:" + FUNC$ + " (@" + STR$(CH%) + ")"
ELSE
IF RANGENUM! <> 0 THEN 'Range specified numerically
RG$ = STR$(RANGENUM!)
ELSE 'Range specified w/ text
RG$ = " " + RANGETXT$
END IF
IF RESOLNUM! <> 0 THEN 'Resolution specified numerically
RES$ = STR$(RESOLNUM!)
ELSE 'Resolution specified w/ text
IF RESOLTXT$ <> "" THEN
RES$ = RESOLTXT$
ELSE
RES$ = ""
END IF
END IF
DDATA$ = "CONF:" + FUNC$ + RG$ + RES$ + ",(@" + STR$(CH%) + ")"
END IF
'Y% = CSRLIN
'LOCATE 22
'PRINT "DVMCONF a" + DDATA$ + "b";
'PRINT SPC(10); : PRINT
'LOCATE Y%
CALL IO488(DDATA$, DASADDR%, 1) 'Write configuration
END SUB
SUB DVMSENS (CH%, FUNC1$, FUNC2$, SETTXT$, SETNUM!)
' Inputs:
' CH% = channel to be measured
' FUNC1$ = parameter to be measured
' FUNC2$ = parameter to be configured
' SETTXT$ = configuration, specified in text
' If "", specified in SETNUM!
' SETNUM! = configuration, specified numerically
' If 0, range specified in SETTXT$
' Outputs: None
' Description - configures HP34970A to take a measurement
'
' Reference;
' DC Voltage and Current Readings
' Integration Time Resolution Digits Bits
' 0.02PLC <0.0001 * Range 4 1/2 15
' 0.2PLC <0.00001 * Range 5 1/2 18
' 1PLC <0.000003 * Range 5 1/2 20
' 2PLC <0.0000022 * Range 6 1/2 21
' 10PLC <0.000001 * Range 6 1/2 24
' 20PLC <0.0000008 * Range 6 1/2 25
' 100PLC <0.0000003 * Range 6 1/2 26
' 200PLC <0.00000022 * Range 6 1/2 26
'
' AC Voltage and Current Readings
' Filter Readings/s Settling Delay Digits
' 3Hz 0.14 1.5s 6 1/2
' 20Hz 1 0.2s 6 1/2
' 200Hz 8 0.02s 6 1/2
IF SETNUM! <> 0 THEN 'specified numerically
ST$ = STR$(SETNUM!)
ELSE 'specified w/ text
ST$ = " " + SETTXT$
END IF
DDATA$ = "SENS:" + FUNC1$ + ":" + FUNC2$ + ST$ + ",(@" + STR$(CH%) + ")"
'Y% = CSRLIN
'LOCATE 23
'PRINT "DVMSENSE a" + DDATA$ + "b";
'PRINT SPC(10); : PRINT
'LOCATE Y%
CALL IO488(DDATA$, DASADDR%, 1) 'Write configuration
END SUB
SUB FUNGEN33120A (CMD$, VALUE!)
' Inputs - String of commands to be sent to the HP33120A
' Outputs - None
' Description - Writes a string of commands to the HP33120A
' function generator, through the IEEE 488 interface
' If VALUE! = -99 then the command is a string only
IF VALUE! = -99 THEN
VALUE2$ = "" 'Command contains no numerical value
ELSEIF CMD$ = FREQ$ AND ABS(VALUE!) < .0001 THEN
VALUE! = .0001 'Func. gen. min output = 100uHz
VALUE2$ = STR$(VALUE!)
ELSEIF (LEFT$(CMD$, 3) = "FSK" OR LEFT$(CMD$, 7) = "FREQ:ST") AND VALUE! <= .0001 THEN
VALUE! = .01 'Func. gen. min FSK value = 10mHz
VALUE2$ = STR$(VALUE!) 'Func. gen. min sweep value = 10mHz
ELSE
VALUE2$ = STR$(VALUE!)
END IF
DDATA$ = CMD$ + " " + VALUE2$
'Y% = CSRLIN
'LOCATE 20
'PRINT "FUNGEN33120A a" + DDATA$ + "b";
'PRINT SPC(10); : PRINT
'LOCATE Y%
CALL IO488(DDATA$, GENADDR%, 1) 'Write data to generator
END SUB
FUNCTION GETREADING!
' Inputs: None
' Outputs: None
' Description - configures HP34970A trigger source, sends a trigger
' command, reads the DVM and returns the reading.
DDATA$ = "TRIG:SOUR BUS" 'Trigger source = bus
CALL IO488(DDATA$, DASADDR%, 1)
DDATA$ = "INIT" 'set 'wait-for-trigger' state
CALL IO488(DDATA$, DASADDR%, 1)
DDATA$ = "*TRG" 'send trigger
CALL IO488(DDATA$, DASADDR%, 1)
DDATA$ = "FETC?" 'retrieve reading
CALL IO488(DDATA$, DASADDR%, 1)
CALL IO488(DDATA$, DASADDR%, 2)
GETREADING! = VAL(DDATA$)
END FUNCTION
SUB HP760APANEL (VALUE!, UNITS%)
'Print a display of the HP760A front panel to show number setting.
'Inputs; VALUE! = Value of voltage or current to be set
' UNITS% 1 = Voltage
' 2 = Current
' 3 = None, FUNCTION = STD BY
' 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75
'
DIM DIG%(7) 'Display digits
TOPLOC% = 16
IF UNITS% = 1 THEN
SCALE! = 1 'Max voltage output is 1000V
LEDTAB% = 34
ELSEIF UNITS% = 2 THEN
SCALE! = 100 'Max current output is 10A
LEDTAB% = 14
ELSE
LEDTAB% = 0
END IF
'Set meter for twice max D.U.T. input
'Set only one digit. Setting other digits
'after most significant digit does not
'always increase output signal magnitude.
IF VALUE! * 2 >= 100 THEN
SETVALUE! = CINT(VALUE! * 2 / 100) * 100 * SCALE!
ELSEIF VALUE! * 2 >= 10 THEN
SETVALUE! = CINT(VALUE! * 2 / 10) * 10 * SCALE!
ELSEIF VALUE! * 2 >= 1 THEN
SETVALUE! = CINT(VALUE! * 2) * SCALE!
ELSEIF VALUE! * 2 >= .1 THEN
SETVALUE! = CINT(VALUE! * 2 * 10) / 10 * SCALE!
ELSEIF VALUE! * 2 >= .01 THEN
SETVALUE! = CINT(VALUE! * 2 * 100) / 100 * SCALE!
ELSEIF VALUE! * 2 >= .001 THEN
SETVALUE! = CINT(VALUE! * 2 * 1000) / 1000 * SCALE!
ELSEIF VALUE! * 2 >= .0001 THEN
SETVALUE! = CINT(VALUE! * 2 * 10000) / 10000 * SCALE!
END IF
NUM$ = STR$(SETVALUE!)
FOR L% = 1 TO 7
DIG%(L%) = 0
NEXT
IF SETVALUE! >= 1000 THEN
DIG%(7) = 9
ELSEIF SETVALUE! >= 100 THEN
DIG%(7) = VAL(MID$(NUM$, 2, 1))
ELSEIF SETVALUE! >= 10 THEN
DIG%(6) = VAL(MID$(NUM$, 2, 1))
ELSEIF SETVALUE! >= 1 THEN
DIG%(5) = VAL(MID$(NUM$, 2, 1))
ELSEIF SETVALUE! >= .1 THEN
DIG%(4) = VAL(MID$(NUM$, 3, 1))
ELSEIF SETVALUE! >= .01 THEN
DIG%(3) = VAL(MID$(NUM$, 4, 1))
ELSEIF SETVALUE! >= .001 THEN
DIG%(3) = VAL(MID$(NUM$, 5, 1))
ELSEIF SETVALUE! >= .0001 THEN
DIG%(3) = VAL(MID$(NUM$, 6, 1))
END IF
FOR L% = 1 TO 7
LOCATE TOPLOC%, (L% - 1) * 10 + 7 'top left corner
PRINT CHR$(201)
FOR M% = 8 TO 10 'top horizontal line
LOCATE TOPLOC%, (L% - 1) * 10 + M%
PRINT CHR$(205)
NEXT M%
LOCATE TOPLOC%, (L% - 1) * 10 + 11 'top right corner
PRINT CHR$(187)
FOR N% = TOPLOC% + 1 TO TOPLOC% + 3
LOCATE N%, (L% - 1) * 10 + 7 'left vertical line
PRINT CHR$(186)
IF N% = TOPLOC% + 2 THEN 'print display digit
LOCATE N%, ((L% - 1) * 10 + 8)
PRINT DIG%(8 - L%)
END IF
LOCATE N%, (L% - 1) * 10 + 11 'right vertical line
PRINT CHR$(186)
NEXT N%
LOCATE TOPLOC% + 4, (L% - 1) * 10 + 7 'bottom left corner
PRINT CHR$(200)
FOR M% = 8 TO 10 'bottom horizontal line
LOCATE TOPLOC% + 4, (L% - 1) * 10 + M%
PRINT CHR$(205)
NEXT M%
LOCATE TOPLOC% + 4, (L% - 1) * 10 + 11 'bottom right corner
PRINT CHR$(188)
NEXT L%
IF LEDTAB% <> 0 THEN
LOCATE TOPLOC% + 2, LEDTAB% 'print LED location
PRINT CHR$(15)
END IF
END SUB
SUB HS1
' Module Name - HS1
'
' Inputs - None
' Outputs - None
' Description - HS1 waits for EOI to occur with ATN = 0
' >>>> Check Bit #4 for true (EOI)
BYTE% = 0
WHILE (BYTE% AND 16) = 0
BYTE% = INP(BADDRS% + 0)
WEND
END SUB
SUB HS2
' Module Name - HS2
'
' Inputs - None
' Outputs - None
' Description - HS1 checks that serial poll active state
' has occured with rsv set in the serial poll register.
' >>>>> Check Bit #5 for true (SPAS)
BYTE% = 0
WHILE NOT BYTE% AND 32 AND BYTE% <> 40
BYTE% = INP(BADDRS% + 0)
WEND
END SUB
SUB INIT488 (DEVADDR%)
' Module Name - INIT488
'
' Inputs - None
' Outputs - None
' Description - Initalizes the IEEE 488 interface
OUT (BADDRS% + 8), &H0 'Configure PC4311 as controller
OUT (BADDRS% + 3), &H80 'Release ACDS holdoff, set operation
OUT (BADDRS% + 3), &H0 'Software reset, set operation
OUT (BADDRS% + 4), DEVADDR% 'Enable dual primary addressing mode
OUT (BADDRS% + 0), &H0 'Disable all interrupts
OUT (BADDRS% + 3), &H8F 'Request control, clear operation
OUT (BADDRS% + 3), &HF 'Send remote enable, clear operation
OUT (BADDRS% + 3), &H90 'Listen only, set operation
END SUB
SUB INITPS (DPSADDR%, OVERI!, OVERV!)
'Initialize Kepco power supply
PS$ = POWERIO$(DPSADDR%, "ZER") 'Clear errors if any
PS$ = POWERIO$(DPSADDR%, "SMD=OC") 'Select over-current protection mode
'Set over-current limit
STLEN% = LEN(STR$(OVERI!))
PS$ = POWERIO$(DPSADDR%, SETIMAX$ + RIGHT$(STR$(OVERI!), STLEN% - 1))
STLEN% = LEN(STR$(OVERV!))
PS$ = POWERIO$(DPSADDR%, "SOV=" + RIGHT$(STR$(OVERV!), STLEN% - 1))'Set over-voltage limit
END SUB
SUB IO488 (DDATA$, DEVADDR%, SEL4%)
'***********************************************************************************************
'* *
'* Description: Output and input to GPIB device using PC4311 GPIB card *
'* *
'* Input : DDATA$ -- Data string to GPIB device *
'* DEVADDR% -- Device address desired *
'* SEL4% -- Write (1) or Read (2) command *
'* *
'* Output : DDATA$ -- Data string from GPIB device *
'* *
'***********************************************************************************************
CALL INIT488(DEVADDR%) 'Address GPIB card
SELECT CASE SEL4%
'Write...
CASE 1
BYTE% = MLA% OR DEVADDR% 'GPIB device address
OUT (BADDRS% + 7), BYTE% 'Send out to GPIB card
CALL HS1 'Wait for recieved status
OUT (BADDRS% + 3), &H8A 'Request control, clear operation
OUT (BADDRS% + 3), &HB 'Remote enable, clear
FOR X1 = 1 TO LEN(DDATA$) 'Create character and send out
BYTE% = ASC(MID$(DDATA$, X1, 1))
OUT (BADDRS% + 7), BYTE%
CALL HS1
NEXT
OUT (BADDRS% + 7), &HD 'Send CR, tell device to execute command
CALL HS1 'Wait for recieved status
OUT (BADDRS% + 7), &HA 'Send LF
CALL HS1 'Wait for recieved status
OUT (BADDRS% + 3), &HC 'Remote enable, set operation
OUT (BADDRS% + 3), &HA 'Clear talk only
OUT (BADDRS% + 7), &H3F 'Un-listen
'Read
CASE 2
DDATA$ = ""
BYTE% = MTA% OR DEVADDR% 'GPIB device address
OUT (BADDRS% + 7), BYTE% 'Send out to GPIB card
CALL HS1 'Wait for recieved status
OUT (BADDRS% + 3), &H89 'Request control, clear
OUT (BADDRS% + 3), &HB 'Remote enable, clear
DO
CALL HS2
DATABYTE = INP(BADDRS% + 7)
IF DATABYTE <> &HA THEN
DDATA$ = DDATA$ + CHR$(DATABYTE)
END IF
LOOP WHILE DATABYTE <> &HA
OUT (BADDRS% + 3), &HC 'Remote enable, set command
OUT (BADDRS% + 3), &H9 'Clear listen only
OUT (BADDRS% + 7), &H5F 'Un-talk command
END SELECT
CALL HS1 'Wait for recieved status
END SUB
FUNCTION KEYBDIN$
'Get input from keyboard
DO
X$ = INKEY$
LOOP WHILE X$ = ""
KEYBDIN$ = UCASE$(X$)
END FUNCTION
SUB pause (TIME!)
T1! = TIMER
DO
T2! = TIMER
LOOP UNTIL (T2! - T1!) > TIME!
END SUB
FUNCTION POWERIO$ (DPSADDR%, CMD$)
'This function sends commands and receives output from the
'Kepco DPS 125-0.5M programmable power supply.
'Inputs: power supply address, command
'Outputs: supply response, if any
'Open and initialize the power supply controller communications channel for I/O
PSCOM$ = "COM" + RIGHT$(STR$(PSPORT%), 1) + ":9600,N,8,1"
OPEN PSCOM$ FOR RANDOM AS #1
DEVSEL = DPSADDR% + &HE0
RXOK = DPSADDR% + &HC0
'DEVSEL must be sent without a <CR>
'prior to each command
'Send DEVSEL byte to DPS
PRINT #1, CHR$(DEVSEL);
N = 0
'Await input buffer to become non-zero
'print message if not successful
DO
IF N > 5000 THEN
PRINT "COM ERROR - INPUT BUFFER EMPTY"
CLOSE #1 'close power supply communication channel
END 'exit program
END IF
N = N + 1
LOOP WHILE (LOC(1) = 0)
'RXOK is returned (without a <CR>) in
'response to DEVSEL
A$ = INPUT$(1, #1) 'Input RXOK byte
PRINT #1, CMD$
'Await input buffer to become non-zero
DO
LOOP WHILE (LOC(1) = 0)
A$ = INPUT$(1, #1) 'Input first (non-ASCII) character of
'string and discard
INPUT #1, B$ 'Input balance of string
POWERIO$ = B$
CLOSE #1 'close power supply communication channel
END FUNCTION
FUNCTION READDVM! (CH%, FUNC$, RANGETXT$, RANGENUM!, RESOLTXT$, RESOLNUM!)
' Inputs:
' CH% = channel to be measured
' FUNC$ = parameter to be measured
' RANGETXT$ = indicate how range is specified. 0 = text, 1 = numerically
' If "", range specified in RANGENUM! or not specified
' RANGENUM! = measurement range
' If 0, range specified in RANGETXT$ or not specified
' RESOLTXT$ = indicate how resolution is specified. 0 = text, 1 = numerically
' IF "", resolution specified in RESOLNUM! or not specified
' RESOLNUM! = measurement resolution
' 0 = Resolution specified in RESOLTXT$ or not specified
' Outputs: None
' Description - configures HP34970A to take a measurement, reads
' the DVM and returns the reading.
'
' Reference;
' MEAS and CONF use the following default instrument settings
' Integration Time 1PLC (5 1/2 digits, 20 bits)
' Input Resistane 10Mohm, fixed for all DCV ranges
' AC Filter 20Hz (medium)
' Scan List Redefined when command executed
' Scan Interval Source Immediate
' Scan Count 1 Scan Sweep
' Channel Delay Automatic Delay
'
' DVM Accuracy;
' The following specs use the 3Hz filter
' ACV +/-0.06% reading +/-0.04% range, 10Hz-20KHz, 0.1V - 100V, 1 year
' ACV +/-0.06% reading +/-0.04% range, 10Hz-20KHz, 0.1V - 100V, 1 year
' ACV +/-0.05% reading +/-0.08% range, 10Hz-20KHz, 300V, 90 day
' ACV +/-0.05% reading +/-0.08% range, 10Hz-20KHz, 300V, 90 day
'
' If the 20Hz filter is used, add the following low frequency error
' +/-0.06% reading, 40Hz-100Hz
' +/-0.01% reading, 100Hz-200Hz
' +/-0.0% reading, 200Hz->1000Hz
IF RANGENUM! <> 0 THEN 'Range specified numerically
RG$ = STR$(RANGENUM!) + ","
ELSE 'Range specified w/ text
RG$ = RANGETXT$ + ","
END IF
IF RESOLNUM! <> 0 THEN 'Resolution specified numerically
RES$ = STR$(RESOLNUM!) + ","
ELSE 'Resolution specified w/ text
IF RESOLTXT$ <> "" THEN
RES$ = RESOLTXT$ + ","
ELSE
RES$ = ""
END IF
END IF
DDATA$ = "MEAS:" + FUNC$ + "?" + RG$ + RES$ + "(@" + STR$(CH%) + ")"
'Y% = CSRLIN
'LOCATE 21
'PRINT "READDVM a" + DDATA$ + "b";
'PRINT SPC(10); : PRINT
'LOCATE Y%
CALL IO488(DDATA$, DASADDR%, 1) 'Write configuration
CALL IO488(DDATA$, DASADDR%, 2) 'Read result
READDVM! = VAL(DDATA$)
END FUNCTION
FUNCTION REPEAT$ (MN$, ELAP2!, TIME2!)
CLS
LOCATE 10, 10
PRINT "Do you want to test another set of "; MN$; " modules?"
LOCATE 23, 49
PRINT USING "Test Time: ## Min. ## Sec."; INT(ELAP2! / 60); ELAP2! - INT(ELAP2! / 60) * 60
DO
A$ = INKEY$
LOOP WHILE A$ = ""
IF UCASE$(A$) <> "N" THEN
LOCATE 12, 10
PRINT "Insert the next set of modules, starting with channel 1."
CALL CONTINUE
END IF
TIME2! = TIMER
REPEAT$ = UCASE$(A$)
END FUNCTION
SUB SETDAC3 (DACNUM%, VOLTAGE!)
'Sets the HP34907 DACs
'Both DACs are;
' +/-12V out
' 10mA max
' 1mV resolution
' ground referenced, cannot float
'
'DAC1 is on CH04, DAC2 is on CH05
'Inputs; DACNUM% 1 = DAC1, 2 = DAC2
' VOLTAGE! Voltage to set
SELECT CASE DACNUM%
CASE 1
CH% = DAC1.CH%
CASE 2
CH% = DAC2.CH%
END SELECT
DDATA$ = SETDAC$ + " " + STR$(VOLTAGE!) + ",(@" + STR$(CH%) + ")"
'Y% = CSRLIN
'LOCATE 21
'PRINT "SETDAC3 a" + DDATA$ + "b";
'PRINT SPC(10); : PRINT
'LOCATE Y%
CALL IO488(DDATA$, DASADDR%, 1) 'Write configuration
END SUB
SUB SETDPS (DPSADDR%, VSUPPLY!)
'Set supply voltage
SETP$ = POWERIO$(DPSADDR%, PSVOLT$ + RIGHT$(STR$(VSUPPLY!), LEN(STR$(VSUPPLY!)) - 1))
SETP$ = POWERIO$(DPSADDR%, SUPPLYON$) 'enable power supply output
CALL pause(.02) '20ms over-current prot. delay
IF POWERIO$(DPSADDR%, ISTAT$) = "RCS=01" THEN 'Check for over-current
SOUND 1000, .5
PRINT
PRINT TAB(10); "Module supply current has exceeded maximum value."
PRINT TAB(10); "Power supply has been disabled."
CALL pause(1)
'PRINT TAB(10); "Press any key to continue"
'A$ = KEYBDIN$
END IF
END SUB
SUB SETSWITCH (CH%, STATE%)
' Inputs:
' CH% = channel to be closed
' STATE% = switch state, 1 = CLOSED, 0 = OPEN
' Outputs: None
' Description - sets the specified switch on the HP34903A 20-channel
' actuator
IF STATE% = 1 THEN 'close switch
DDATA$ = "ROUT:CLOS " + "(@" + STR$(CH%) + ")"
ELSE 'String command only
DDATA$ = "ROUT:OPEN " + "(@" + STR$(CH%) + ")"
END IF
'Y% = CSRLIN
'LOCATE 22
'PRINT "SETSWITCH a" + DDATA$ + "b";
'PRINT SPC(10); : PRINT
'LOCATE Y%
CALL IO488(DDATA$, DASADDR%, 1) 'Write configuration
END SUB
FUNCTION UPSN$ (SN$, NUMDUT%)
'SN$ = NNNN-DD
N% = 1
WHILE MID$(SN$, N%, 1) <> "-"
' PRINT MID$(SN$, N%, 1)
SNTEMP$ = SNTEMP$ + MID$(SN$, N%, 1)
N% = N% + 1
WEND
L% = LEN(SN$)
UPSN$ = SNTEMP$ + "-" + LTRIM$(STR$(VAL(MID$(SN$, N% + 1, L% - N%)) + NUMDUT%))
END FUNCTION

View File

@@ -0,0 +1,31 @@
High Input Voltage Units test program directory containing the archived and
release versions of the test and database programs.
Directory structure:
HVDATA: Current version of the high input voltage database file ("hvin.dat") is in this directory, while the archived previous versions are under the "History" subdirectory ("...\HVU\HVDATA\History").
Release: The current released test program source files and executable are in this directory, while the archived previous versions are under the "History" subdirectory ("...\HVU\Release\History").
DBpgm: The current released high input voltage database modification program source files and executable are in this directory, while the archived previous versions are under the "History" subdirectory ("...\HVU\DBpgm\History").
NOTE: The ongoing structure and naming convention for the archived ("history") files should include ALL of the source files (including the ".MAK" file) and the executable file for that version of the program, which should be located in a folder named for the date of release in this format: "year-month-date" with a four-digit year, two digit month (leading zero if required), and a two-digit date (leading zero if required). For example, "2014-07-31" for July 31, 2014. Maintaining this format will lead to consistent sorting when attempting to locate versions in the future. This folder should be created under the appropriate "History" subfolder (under "...\HVU\DBpgm\History" for database programs, ...\HVU\Release\History" for test programs, or "...\HVU\HVDATA\History" for database files.
FURTHER NOTE: Previously archived test and/or database programs do not necessarily follow the structure described above, and are all located under the "...\HVU\Released\History\_Old (Pre 2014-07-31)" folder.
Program release instructions:
-----------------------------
The updated test program executable (.EXE) file is copied to:
"T:\ENGR\ATE\ProdSW\ATE"
Then the batch file "TtoK.bat" is run (double-click on the file name in Window Explorer) in folder:
"T:\ENGR\ATE\ProdSW\ATE Software Transfer Programs"
Database release instructions:
-----------------------------
The updated test database (.DAT) file is copied to:
"T:\ENGR\ATE\ProdSW\ATE\HVDATA"
Then the batch file "TtoK.bat" is run (double-click on the file name in Window Explorer) in folder:
"T:\ENGR\ATE\ProdSW\ATE Software Transfer Programs"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
TESTHV3.BAS
TESTHV4.BAS
NLIBATE3.BAS

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
"""SSH helper for AD2 access. Usage: python ssh_ad2.py <cmd>"""
import subprocess, sys
import paramiko, yaml
HOST = '192.168.0.6'
USER = 'sysadmin'
def _pwd():
r = subprocess.run(['sops','-d','D:/vault/clients/dataforth/ad2.sops.yaml'],
capture_output=True, text=True, timeout=30, check=True)
return yaml.safe_load(r.stdout)['credentials']['password'].replace('\\','')
PWD = _pwd()
def run(cmd, timeout=60):
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(HOST, username=USER, password=PWD, timeout=15, look_for_keys=False, allow_agent=False)
try:
stdin, stdout, stderr = c.exec_command(cmd, timeout=timeout)
out = stdout.read().decode(errors='replace')
err = stderr.read().decode(errors='replace')
rc = stdout.channel.recv_exit_status()
return rc, out, err
finally:
c.close()
def pull(remote_path, local_path):
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(HOST, username=USER, password=PWD, timeout=15, look_for_keys=False, allow_agent=False)
try:
sftp = c.open_sftp()
sftp.get(remote_path, local_path)
sftp.close()
finally:
c.close()
if __name__ == '__main__':
if sys.argv[1] == 'pull':
pull(sys.argv[2], sys.argv[3])
print(f'[OK] pulled {sys.argv[2]} -> {sys.argv[3]}')
else:
rc, out, err = run(' '.join(sys.argv[1:]))
if out: print(out, end='')
if err: print('STDERR:', err, file=sys.stderr, end='')
sys.exit(rc)