Replace DB-backed instrument upserts with app.yaml-driven config loading, matching, and translator application in the ingestion workflow. Also add serial-port connector support, startup validation tooling, and migration tracking updates to keep runtime behavior and docs aligned.
153 lines
4.6 KiB
JavaScript
153 lines
4.6 KiB
JavaScript
const fs = require('fs');
|
|
const config = require('../../config/app');
|
|
|
|
function normalizeConnectorType(type) {
|
|
const value = String(type || '').trim().toLowerCase();
|
|
if (value === 'serial' || value === 'astm-serial') return 'astm-serial';
|
|
if (value === 'tcp-server' || value === 'hl7-tcp') return 'hl7-tcp';
|
|
if (value === 'http-json' || value === 'http') return 'http-json';
|
|
if (value === 'tcp-client') return 'tcp-client';
|
|
return '';
|
|
}
|
|
|
|
function defaultParserForConnector(connector) {
|
|
if (connector === 'astm-serial') return 'astm';
|
|
if (connector === 'hl7-tcp') return 'hl7';
|
|
if (connector === 'http-json') return 'http-json';
|
|
return '';
|
|
}
|
|
|
|
function toEntityRows(entities = []) {
|
|
return entities.map((entity) => {
|
|
const connector = entity.connector && typeof entity.connector === 'object' ? entity.connector : {};
|
|
return {
|
|
instrument_id: entity.instrument_id,
|
|
enabled: entity.enabled,
|
|
connector: connector.type,
|
|
connectorConfig: connector,
|
|
config: entity.config,
|
|
translator: entity.translator
|
|
};
|
|
});
|
|
}
|
|
|
|
class InstrumentConfigValidationError extends Error {
|
|
constructor(errors) {
|
|
super(`instrument config validation failed with ${errors.length} issue(s)`);
|
|
this.name = 'InstrumentConfigValidationError';
|
|
this.errors = errors;
|
|
}
|
|
}
|
|
|
|
function validateAndLoadInstrumentConfigs({
|
|
instruments = config.instrumentEntities?.length ? toEntityRows(config.instrumentEntities) : config.instruments,
|
|
configFilePath = config.configPath
|
|
} = {}) {
|
|
const errors = [];
|
|
const entries = [];
|
|
const instrumentIds = new Set();
|
|
|
|
if (configFilePath && !fs.existsSync(configFilePath)) {
|
|
errors.push(`config file not found: ${configFilePath}`);
|
|
throw new InstrumentConfigValidationError(errors);
|
|
}
|
|
|
|
if (!Array.isArray(instruments)) {
|
|
errors.push('config.instruments: expected an array');
|
|
throw new InstrumentConfigValidationError(errors);
|
|
}
|
|
|
|
if (!instruments.length) {
|
|
errors.push('config.instruments: array cannot be empty');
|
|
}
|
|
|
|
for (let index = 0; index < instruments.length; index += 1) {
|
|
const item = instruments[index];
|
|
const label = `instrument[${index}]`;
|
|
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
errors.push(`${label}: must be an object`);
|
|
continue;
|
|
}
|
|
|
|
const instrumentId = item.instrument_id;
|
|
const connector = normalizeConnectorType(item.connector);
|
|
const translator = item.translator;
|
|
|
|
if (!instrumentId || typeof instrumentId !== 'string') {
|
|
errors.push(`${label}: instrument_id is required`);
|
|
continue;
|
|
}
|
|
if (instrumentIds.has(instrumentId)) {
|
|
errors.push(`${label}: duplicate instrument_id "${instrumentId}"`);
|
|
continue;
|
|
}
|
|
instrumentIds.add(instrumentId);
|
|
|
|
if (!connector) {
|
|
errors.push(`${label}: connector.type is required`);
|
|
continue;
|
|
}
|
|
|
|
if (connector === 'tcp-client') {
|
|
errors.push(`${label}: connector.type tcp-client is not supported yet`);
|
|
continue;
|
|
}
|
|
|
|
if (!translator || typeof translator !== 'object' || Array.isArray(translator)) {
|
|
item.translator = { parser: defaultParserForConnector(connector) };
|
|
}
|
|
|
|
const resolvedTranslator = item.translator;
|
|
|
|
if (!resolvedTranslator.parser || typeof resolvedTranslator.parser !== 'string') {
|
|
errors.push(`${label}: translator.parser is required`);
|
|
continue;
|
|
}
|
|
|
|
const connectorConfig = item.connectorConfig && typeof item.connectorConfig === 'object'
|
|
? item.connectorConfig
|
|
: {};
|
|
const match = item.match && typeof item.match === 'object' ? { ...item.match } : {};
|
|
|
|
if (connector === 'astm-serial') {
|
|
const comPort = connectorConfig.port || connectorConfig.comPort;
|
|
if (!comPort || typeof comPort !== 'string') {
|
|
errors.push(`${label}: connector.port is required for serial`);
|
|
continue;
|
|
}
|
|
match.comPort = comPort;
|
|
}
|
|
|
|
if (connector === 'hl7-tcp' || connector === 'http-json') {
|
|
const localPort = connectorConfig.port || connectorConfig.localPort;
|
|
if (localPort !== undefined && localPort !== null && localPort !== '') {
|
|
match.localPort = Number(localPort);
|
|
}
|
|
}
|
|
|
|
entries.push({
|
|
instrument_id: instrumentId,
|
|
connector,
|
|
enabled: item.enabled !== false,
|
|
match,
|
|
config: item.config || {},
|
|
translator: resolvedTranslator,
|
|
connectorConfig,
|
|
files: {
|
|
config: configFilePath
|
|
}
|
|
});
|
|
}
|
|
|
|
if (errors.length) {
|
|
throw new InstrumentConfigValidationError(errors);
|
|
}
|
|
|
|
return entries;
|
|
}
|
|
|
|
module.exports = {
|
|
InstrumentConfigValidationError,
|
|
validateAndLoadInstrumentConfigs
|
|
};
|