updates
This commit is contained in:
@@ -1,5 +1,27 @@
|
||||
const EventEmitter = require('events');
|
||||
const {loadCurve,gravity,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils,coolprop} = require('generalFunctions');
|
||||
const {loadCurve,gravity,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils,coolprop, convert} = require('generalFunctions');
|
||||
|
||||
const CANONICAL_UNITS = Object.freeze({
|
||||
pressure: 'Pa',
|
||||
atmPressure: 'Pa',
|
||||
flow: 'm3/s',
|
||||
power: 'W',
|
||||
temperature: 'K',
|
||||
});
|
||||
|
||||
const DEFAULT_IO_UNITS = Object.freeze({
|
||||
pressure: 'mbar',
|
||||
flow: 'm3/h',
|
||||
power: 'kW',
|
||||
temperature: 'C',
|
||||
});
|
||||
|
||||
const DEFAULT_CURVE_UNITS = Object.freeze({
|
||||
pressure: 'mbar',
|
||||
flow: 'm3/h',
|
||||
power: 'kW',
|
||||
control: '%',
|
||||
});
|
||||
|
||||
/**
|
||||
* Rotating machine domain model.
|
||||
@@ -21,15 +43,25 @@ class Machine {
|
||||
|
||||
// Load a specific curve
|
||||
this.model = machineConfig.asset.model; // Get the model from the machineConfig
|
||||
this.curve = this.model ? loadCurve(this.model) : null; // we need to convert the curve and add units to the curve information
|
||||
this.rawCurve = this.model ? loadCurve(this.model) : null;
|
||||
this.curve = null;
|
||||
|
||||
//Init config and check if it is valid
|
||||
this.config = this.configUtils.initConfig(machineConfig);
|
||||
|
||||
//add unique name for this node.
|
||||
this.config = this.configUtils.updateConfig(this.config, {general:{name: this.config.functionality?.softwareType + "_" + machineConfig.general.id}}); // add unique name if not present
|
||||
this.unitPolicy = this._buildUnitPolicy(this.config);
|
||||
this.config = this.configUtils.updateConfig(this.config, {
|
||||
general: { unit: this.unitPolicy.output.flow },
|
||||
asset: {
|
||||
...this.config.asset,
|
||||
unit: this.unitPolicy.output.flow,
|
||||
curveUnits: this.unitPolicy.curve,
|
||||
},
|
||||
});
|
||||
|
||||
if (!this.model || !this.curve) {
|
||||
if (!this.model || !this.rawCurve) {
|
||||
this.logger.error(`${!this.model ? 'Model not specified' : 'Curve not found for model ' + this.model} in machineConfig. Cannot make predictions.`);
|
||||
// Set prediction objects to null to prevent method calls
|
||||
this.predictFlow = null;
|
||||
@@ -38,12 +70,21 @@ class Machine {
|
||||
this.hasCurve = false;
|
||||
}
|
||||
else{
|
||||
this.hasCurve = true;
|
||||
this.config = this.configUtils.updateConfig(this.config, { asset: { ...this.config.asset, machineCurve: this.curve } });
|
||||
//machineConfig = { ...machineConfig, asset: { ...machineConfig.asset, machineCurve: this.curve } }; // Merge curve into machineConfig
|
||||
this.predictFlow = new predict({ curve: this.config.asset.machineCurve.nq }); // load nq (x : ctrl , y : flow relationship)
|
||||
this.predictPower = new predict({ curve: this.config.asset.machineCurve.np }); // load np (x : ctrl , y : power relationship)
|
||||
this.predictCtrl = new predict({ curve: this.reverseCurve(this.config.asset.machineCurve.nq) }); // load reversed nq (x: flow, y: ctrl relationship)
|
||||
try {
|
||||
this.hasCurve = true;
|
||||
this.curve = this._normalizeMachineCurve(this.rawCurve);
|
||||
this.config = this.configUtils.updateConfig(this.config, { asset: { ...this.config.asset, machineCurve: this.curve } });
|
||||
//machineConfig = { ...machineConfig, asset: { ...machineConfig.asset, machineCurve: this.curve } }; // Merge curve into machineConfig
|
||||
this.predictFlow = new predict({ curve: this.config.asset.machineCurve.nq }); // load nq (x : ctrl , y : flow relationship)
|
||||
this.predictPower = new predict({ curve: this.config.asset.machineCurve.np }); // load np (x : ctrl , y : power relationship)
|
||||
this.predictCtrl = new predict({ curve: this.reverseCurve(this.config.asset.machineCurve.nq) }); // load reversed nq (x: flow, y: ctrl relationship)
|
||||
} catch (error) {
|
||||
this.logger.error(`Curve normalization failed for model '${this.model}': ${error.message}`);
|
||||
this.predictFlow = null;
|
||||
this.predictPower = null;
|
||||
this.predictCtrl = null;
|
||||
this.hasCurve = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.state = new state(stateConfig, this.logger); // Init State manager and pass logger
|
||||
@@ -54,16 +95,55 @@ class Machine {
|
||||
autoConvert: true,
|
||||
windowSize: 50,
|
||||
defaultUnits: {
|
||||
pressure: 'mbar',
|
||||
flow: this.config.general.unit,
|
||||
power: 'kW',
|
||||
temperature: 'C'
|
||||
}
|
||||
});
|
||||
pressure: this.unitPolicy.output.pressure,
|
||||
flow: this.unitPolicy.output.flow,
|
||||
power: this.unitPolicy.output.power,
|
||||
temperature: this.unitPolicy.output.temperature,
|
||||
atmPressure: 'Pa',
|
||||
},
|
||||
preferredUnits: {
|
||||
pressure: this.unitPolicy.output.pressure,
|
||||
flow: this.unitPolicy.output.flow,
|
||||
power: this.unitPolicy.output.power,
|
||||
temperature: this.unitPolicy.output.temperature,
|
||||
atmPressure: 'Pa',
|
||||
},
|
||||
canonicalUnits: this.unitPolicy.canonical,
|
||||
storeCanonical: true,
|
||||
strictUnitValidation: true,
|
||||
throwOnInvalidUnit: true,
|
||||
requireUnitForTypes: ['pressure', 'flow', 'power', 'temperature', 'atmPressure'],
|
||||
}, this.logger);
|
||||
|
||||
this.interpolation = new interpolation();
|
||||
|
||||
this.flowDrift = null;
|
||||
this.powerDrift = null;
|
||||
this.pressureDrift = { level: 0, flags: ["nominal"], source: null };
|
||||
this.driftProfiles = {
|
||||
flow: {
|
||||
windowSize: 30,
|
||||
minSamplesForLongTerm: 10,
|
||||
ewmaAlpha: 0.15,
|
||||
alignmentToleranceMs: 2500,
|
||||
strictValidation: true,
|
||||
},
|
||||
power: {
|
||||
windowSize: 30,
|
||||
minSamplesForLongTerm: 10,
|
||||
ewmaAlpha: 0.15,
|
||||
alignmentToleranceMs: 2500,
|
||||
strictValidation: true,
|
||||
},
|
||||
};
|
||||
this.errorMetrics.registerMetric("flow", this.driftProfiles.flow);
|
||||
this.errorMetrics.registerMetric("power", this.driftProfiles.power);
|
||||
this.predictionHealth = {
|
||||
quality: "invalid",
|
||||
confidence: 0,
|
||||
pressureSource: null,
|
||||
flags: ["not_initialized"],
|
||||
};
|
||||
|
||||
this.currentMode = this.config.mode.current;
|
||||
this.currentEfficiencyCurve = {};
|
||||
@@ -101,6 +181,7 @@ class Machine {
|
||||
upstream: new Set(),
|
||||
downstream: new Set(),
|
||||
};
|
||||
this.childMeasurementListeners = new Map();
|
||||
this._initVirtualPressureChildren();
|
||||
|
||||
|
||||
@@ -113,12 +194,23 @@ class Machine {
|
||||
const measurements = new MeasurementContainer({
|
||||
autoConvert: true,
|
||||
defaultUnits: {
|
||||
pressure: "mbar",
|
||||
flow: this.config.general.unit,
|
||||
power: "kW",
|
||||
temperature: "C",
|
||||
pressure: this.unitPolicy.output.pressure,
|
||||
flow: this.unitPolicy.output.flow,
|
||||
power: this.unitPolicy.output.power,
|
||||
temperature: this.unitPolicy.output.temperature,
|
||||
},
|
||||
});
|
||||
preferredUnits: {
|
||||
pressure: this.unitPolicy.output.pressure,
|
||||
flow: this.unitPolicy.output.flow,
|
||||
power: this.unitPolicy.output.power,
|
||||
temperature: this.unitPolicy.output.temperature,
|
||||
},
|
||||
canonicalUnits: this.unitPolicy.canonical,
|
||||
storeCanonical: true,
|
||||
strictUnitValidation: true,
|
||||
throwOnInvalidUnit: true,
|
||||
requireUnitForTypes: ['pressure'],
|
||||
}, this.logger);
|
||||
|
||||
measurements.setChildId(id);
|
||||
measurements.setChildName(name);
|
||||
@@ -133,7 +225,7 @@ class Machine {
|
||||
},
|
||||
asset: {
|
||||
type: "pressure",
|
||||
unit: "mbar",
|
||||
unit: this.unitPolicy.output.pressure,
|
||||
},
|
||||
},
|
||||
measurements,
|
||||
@@ -151,14 +243,14 @@ class Machine {
|
||||
|
||||
_init(){
|
||||
//assume standard temperature is 20degrees
|
||||
this.measurements.type('temperature').variant('measured').position('atEquipment').value(15).unit('C');
|
||||
this.measurements.type('temperature').variant('measured').position('atEquipment').value(15, Date.now(), this.unitPolicy.output.temperature);
|
||||
//assume standard atm pressure is at sea level
|
||||
this.measurements.type('atmPressure').variant('measured').position('atEquipment').value(101325).unit('Pa');
|
||||
this.measurements.type('atmPressure').variant('measured').position('atEquipment').value(101325, Date.now(), 'Pa');
|
||||
//populate min and max when curve data is available
|
||||
const flowunit = this.config.general.unit;
|
||||
const flowunit = this.unitPolicy.canonical.flow;
|
||||
if (this.predictFlow) {
|
||||
this.measurements.type('flow').variant('predicted').position('max').value(this.predictFlow.currentFxyYMax, Date.now() , flowunit);
|
||||
this.measurements.type('flow').variant('predicted').position('min').value(this.predictFlow.currentFxyYMin).unit(this.config.general.unit);
|
||||
this.measurements.type('flow').variant('predicted').position('min').value(this.predictFlow.currentFxyYMin, Date.now(), flowunit);
|
||||
} else {
|
||||
this.measurements.type('flow').variant('predicted').position('max').value(0, Date.now(), flowunit);
|
||||
this.measurements.type('flow').variant('predicted').position('min').value(0, Date.now(), flowunit);
|
||||
@@ -169,9 +261,11 @@ class Machine {
|
||||
const isOperational = this._isOperationalState();
|
||||
if(!isOperational){
|
||||
//overrule the last prediction this should be 0 now
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(0,Date.now(),this.config.general.unit);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(0,Date.now(),this.config.general.unit);
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(0,Date.now(),this.unitPolicy.canonical.flow);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(0,Date.now(),this.unitPolicy.canonical.flow);
|
||||
this.measurements.type("power").variant("predicted").position("atEquipment").value(0,Date.now(),this.unitPolicy.canonical.power);
|
||||
}
|
||||
this._updatePredictionHealth();
|
||||
}
|
||||
|
||||
/*------------------- Register child events -------------------*/
|
||||
@@ -191,24 +285,31 @@ class Machine {
|
||||
|
||||
//rebuild to measurementype.variant no position and then switch based on values not strings or names.
|
||||
const eventName = `${measurementType}.measured.${position}`;
|
||||
const listenerKey = `${childId}:${eventName}`;
|
||||
const existingListener = this.childMeasurementListeners.get(listenerKey);
|
||||
if (existingListener) {
|
||||
if (typeof existingListener.emitter.off === "function") {
|
||||
existingListener.emitter.off(existingListener.eventName, existingListener.handler);
|
||||
} else if (typeof existingListener.emitter.removeListener === "function") {
|
||||
existingListener.emitter.removeListener(existingListener.eventName, existingListener.handler);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.debug(`Setting up listener for ${eventName} from child ${child.config.general.name}`);
|
||||
// Register event listener for measurement updates
|
||||
child.measurements.emitter.on(eventName, (eventData) => {
|
||||
const listener = (eventData) => {
|
||||
this.logger.debug(`🔄 ${position} ${measurementType} from ${eventData.childName}: ${eventData.value} ${eventData.unit}`);
|
||||
|
||||
|
||||
this.logger.debug(` Emitting... ${eventName} with data:`);
|
||||
// Store directly in parent's measurement container
|
||||
this.measurements
|
||||
.type(measurementType)
|
||||
.variant("measured")
|
||||
.position(position)
|
||||
.child(childId)
|
||||
.value(eventData.value, eventData.timestamp, eventData.unit);
|
||||
|
||||
// Call the appropriate handler
|
||||
// Route through centralized handlers so unit validation/conversion is applied once.
|
||||
this._callMeasurementHandler(measurementType, eventData.value, position, eventData);
|
||||
};
|
||||
child.measurements.emitter.on(eventName, listener);
|
||||
this.childMeasurementListeners.set(listenerKey, {
|
||||
emitter: child.measurements.emitter,
|
||||
eventName,
|
||||
handler: listener,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -223,6 +324,10 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
case 'flow':
|
||||
this.updateMeasuredFlow(value, position, context);
|
||||
break;
|
||||
|
||||
case 'power':
|
||||
this.updateMeasuredPower(value, position, context);
|
||||
break;
|
||||
|
||||
case 'temperature':
|
||||
this.updateMeasuredTemperature(value, position, context);
|
||||
@@ -238,21 +343,352 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
|
||||
//---------------- END child stuff -------------//
|
||||
|
||||
// Method to assess drift using errorMetrics
|
||||
assessDrift(measurement, processMin, processMax) {
|
||||
this.logger.debug(`Assessing drift for measurement: ${measurement} processMin: ${processMin} processMax: ${processMax}`);
|
||||
const predictedMeasurement = this.measurements.type(measurement).variant("predicted").position("downstream").getAllValues().values;
|
||||
const measuredMeasurement = this.measurements.type(measurement).variant("measured").position("downstream").getAllValues().values;
|
||||
_buildUnitPolicy(config) {
|
||||
const flowOutputUnit = this._resolveUnitOrFallback(
|
||||
config?.general?.unit,
|
||||
'volumeFlowRate',
|
||||
DEFAULT_IO_UNITS.flow,
|
||||
'general.flow'
|
||||
);
|
||||
const pressureOutputUnit = this._resolveUnitOrFallback(
|
||||
config?.asset?.pressureUnit,
|
||||
'pressure',
|
||||
DEFAULT_IO_UNITS.pressure,
|
||||
'asset.pressure'
|
||||
);
|
||||
const powerOutputUnit = this._resolveUnitOrFallback(
|
||||
config?.asset?.powerUnit,
|
||||
'power',
|
||||
DEFAULT_IO_UNITS.power,
|
||||
'asset.power'
|
||||
);
|
||||
const temperatureOutputUnit = this._resolveUnitOrFallback(
|
||||
config?.asset?.temperatureUnit,
|
||||
'temperature',
|
||||
DEFAULT_IO_UNITS.temperature,
|
||||
'asset.temperature'
|
||||
);
|
||||
const curveUnits = this._resolveCurveUnits(config?.asset?.curveUnits || {}, flowOutputUnit);
|
||||
|
||||
if (!predictedMeasurement || !measuredMeasurement) return null;
|
||||
|
||||
return this.errorMetrics.assessDrift(
|
||||
predictedMeasurement,
|
||||
measuredMeasurement,
|
||||
processMin,
|
||||
processMax
|
||||
);
|
||||
return {
|
||||
canonical: { ...CANONICAL_UNITS },
|
||||
output: {
|
||||
pressure: pressureOutputUnit,
|
||||
flow: flowOutputUnit,
|
||||
power: powerOutputUnit,
|
||||
temperature: temperatureOutputUnit,
|
||||
atmPressure: 'Pa',
|
||||
},
|
||||
curve: curveUnits,
|
||||
};
|
||||
}
|
||||
|
||||
_resolveCurveUnits(curveUnits = {}, fallbackFlowUnit = DEFAULT_CURVE_UNITS.flow) {
|
||||
const pressure = this._resolveUnitOrFallback(
|
||||
curveUnits.pressure,
|
||||
'pressure',
|
||||
DEFAULT_CURVE_UNITS.pressure,
|
||||
'asset.curveUnits.pressure'
|
||||
);
|
||||
const flow = this._resolveUnitOrFallback(
|
||||
curveUnits.flow,
|
||||
'volumeFlowRate',
|
||||
fallbackFlowUnit || DEFAULT_CURVE_UNITS.flow,
|
||||
'asset.curveUnits.flow'
|
||||
);
|
||||
const power = this._resolveUnitOrFallback(
|
||||
curveUnits.power,
|
||||
'power',
|
||||
DEFAULT_CURVE_UNITS.power,
|
||||
'asset.curveUnits.power'
|
||||
);
|
||||
const control = typeof curveUnits.control === 'string' && curveUnits.control.trim()
|
||||
? curveUnits.control.trim()
|
||||
: DEFAULT_CURVE_UNITS.control;
|
||||
|
||||
return { pressure, flow, power, control };
|
||||
}
|
||||
|
||||
_resolveUnitOrFallback(candidate, expectedMeasure, fallbackUnit, label) {
|
||||
const fallback = String(fallbackUnit || '').trim();
|
||||
const raw = typeof candidate === 'string' ? candidate.trim() : '';
|
||||
if (!raw) return fallback;
|
||||
try {
|
||||
const desc = convert().describe(raw);
|
||||
if (expectedMeasure && desc.measure !== expectedMeasure) {
|
||||
throw new Error(`expected ${expectedMeasure} but got ${desc.measure}`);
|
||||
}
|
||||
return raw;
|
||||
} catch (error) {
|
||||
this.logger.warn(`Invalid ${label} unit '${raw}' (${error.message}). Falling back to '${fallback}'.`);
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
_convertUnitValue(value, fromUnit, toUnit, contextLabel = 'unit conversion') {
|
||||
const numeric = Number(value);
|
||||
if (!Number.isFinite(numeric)) {
|
||||
throw new Error(`${contextLabel}: value '${value}' is not finite`);
|
||||
}
|
||||
if (!fromUnit || !toUnit || fromUnit === toUnit) {
|
||||
return numeric;
|
||||
}
|
||||
return convert(numeric).from(fromUnit).to(toUnit);
|
||||
}
|
||||
|
||||
_normalizeCurveSection(section, fromYUnit, toYUnit, fromPressureUnit, toPressureUnit, sectionName) {
|
||||
const normalized = {};
|
||||
for (const [pressureKey, pair] of Object.entries(section || {})) {
|
||||
const canonicalPressure = this._convertUnitValue(
|
||||
Number(pressureKey),
|
||||
fromPressureUnit,
|
||||
toPressureUnit,
|
||||
`${sectionName} pressure axis`
|
||||
);
|
||||
const xArray = Array.isArray(pair?.x) ? pair.x.map(Number) : [];
|
||||
const yArray = Array.isArray(pair?.y) ? pair.y.map((v) => this._convertUnitValue(v, fromYUnit, toYUnit, `${sectionName} output`)) : [];
|
||||
if (!xArray.length || !yArray.length || xArray.length !== yArray.length) {
|
||||
throw new Error(`Invalid ${sectionName} section at pressure '${pressureKey}'.`);
|
||||
}
|
||||
normalized[String(canonicalPressure)] = {
|
||||
x: xArray,
|
||||
y: yArray,
|
||||
};
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
_normalizeMachineCurve(rawCurve, curveUnits = this.unitPolicy.curve) {
|
||||
if (!rawCurve || typeof rawCurve !== 'object' || !rawCurve.nq || !rawCurve.np) {
|
||||
throw new Error('Machine curve is missing required nq/np sections.');
|
||||
}
|
||||
return {
|
||||
nq: this._normalizeCurveSection(
|
||||
rawCurve.nq,
|
||||
curveUnits.flow,
|
||||
this.unitPolicy.canonical.flow,
|
||||
curveUnits.pressure,
|
||||
this.unitPolicy.canonical.pressure,
|
||||
'nq'
|
||||
),
|
||||
np: this._normalizeCurveSection(
|
||||
rawCurve.np,
|
||||
curveUnits.power,
|
||||
this.unitPolicy.canonical.power,
|
||||
curveUnits.pressure,
|
||||
this.unitPolicy.canonical.pressure,
|
||||
'np'
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
isUnitValidForType(type, unit) {
|
||||
return this.measurements?.isUnitCompatible?.(type, unit) === true;
|
||||
}
|
||||
|
||||
_resolveMeasurementUnit(type, providedUnit) {
|
||||
const unit = typeof providedUnit === 'string' ? providedUnit.trim() : '';
|
||||
if (!unit) {
|
||||
throw new Error(`Missing unit for ${type} measurement.`);
|
||||
}
|
||||
if (!this.isUnitValidForType(type, unit)) {
|
||||
throw new Error(`Unsupported unit '${unit}' for ${type} measurement.`);
|
||||
}
|
||||
return unit;
|
||||
}
|
||||
|
||||
_measurementPositionForMetric(metricId) {
|
||||
if (metricId === "power") return "atEquipment";
|
||||
return "downstream";
|
||||
}
|
||||
|
||||
_resolveProcessRangeForMetric(metricId, predictedValue, measuredValue) {
|
||||
let processMin = NaN;
|
||||
let processMax = NaN;
|
||||
|
||||
if (metricId === "flow") {
|
||||
processMin = Number(this.predictFlow?.currentFxyYMin);
|
||||
processMax = Number(this.predictFlow?.currentFxyYMax);
|
||||
} else if (metricId === "power") {
|
||||
processMin = Number(this.predictPower?.currentFxyYMin);
|
||||
processMax = Number(this.predictPower?.currentFxyYMax);
|
||||
}
|
||||
|
||||
if (!Number.isFinite(processMin) || !Number.isFinite(processMax) || processMax <= processMin) {
|
||||
const p = Number(predictedValue);
|
||||
const m = Number(measuredValue);
|
||||
const localMin = Math.min(p, m);
|
||||
const localMax = Math.max(p, m);
|
||||
processMin = Number.isFinite(localMin) ? localMin : 0;
|
||||
processMax = Number.isFinite(localMax) && localMax > processMin ? localMax : processMin + 1;
|
||||
}
|
||||
|
||||
return { processMin, processMax };
|
||||
}
|
||||
|
||||
_updateMetricDrift(metricId, measuredValue, context = {}) {
|
||||
const position = this._measurementPositionForMetric(metricId);
|
||||
const predictedValue = Number(
|
||||
this.measurements
|
||||
.type(metricId)
|
||||
.variant("predicted")
|
||||
.position(position)
|
||||
.getCurrentValue()
|
||||
);
|
||||
const measured = Number(measuredValue);
|
||||
if (!Number.isFinite(predictedValue) || !Number.isFinite(measured)) return null;
|
||||
|
||||
const { processMin, processMax } = this._resolveProcessRangeForMetric(metricId, predictedValue, measured);
|
||||
const timestamp = Number(context.timestamp || Date.now());
|
||||
const profile = this.driftProfiles[metricId] || {};
|
||||
|
||||
try {
|
||||
const drift = this.errorMetrics.assessPoint(metricId, predictedValue, measured, {
|
||||
...profile,
|
||||
processMin,
|
||||
processMax,
|
||||
predictedTimestamp: timestamp,
|
||||
measuredTimestamp: timestamp,
|
||||
});
|
||||
|
||||
if (drift && drift.valid) {
|
||||
if (metricId === "flow") this.flowDrift = drift;
|
||||
if (metricId === "power") this.powerDrift = drift;
|
||||
}
|
||||
|
||||
return drift;
|
||||
} catch (error) {
|
||||
this.logger.warn(`Drift update failed for metric '${metricId}': ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
_updatePressureDriftStatus() {
|
||||
const status = this.getPressureInitializationStatus();
|
||||
const flags = [];
|
||||
let level = 0;
|
||||
|
||||
if (!status.initialized) {
|
||||
level = 2;
|
||||
flags.push("no_pressure_input");
|
||||
} else if (!status.hasDifferential) {
|
||||
level = 1;
|
||||
flags.push("single_side_pressure");
|
||||
}
|
||||
|
||||
if (status.hasDifferential) {
|
||||
const upstream = this._getPreferredPressureValue("upstream");
|
||||
const downstream = this._getPreferredPressureValue("downstream");
|
||||
const diff = Number(downstream) - Number(upstream);
|
||||
if (Number.isFinite(diff) && diff < 0) {
|
||||
level = Math.max(level, 3);
|
||||
flags.push("negative_pressure_differential");
|
||||
}
|
||||
}
|
||||
|
||||
this.pressureDrift = {
|
||||
level,
|
||||
source: status.source,
|
||||
flags: flags.length ? flags : ["nominal"],
|
||||
};
|
||||
|
||||
return this.pressureDrift;
|
||||
}
|
||||
|
||||
assessDrift(measurement, processMin, processMax) {
|
||||
const metricId = String(measurement || "").toLowerCase();
|
||||
const position = this._measurementPositionForMetric(metricId);
|
||||
const predictedMeasurement = this.measurements.type(metricId).variant("predicted").position(position).getAllValues();
|
||||
const measuredMeasurement = this.measurements.type(metricId).variant("measured").position(position).getAllValues();
|
||||
|
||||
if (!predictedMeasurement?.values || !measuredMeasurement?.values) return null;
|
||||
|
||||
return this.errorMetrics.assessDrift(
|
||||
predictedMeasurement.values,
|
||||
measuredMeasurement.values,
|
||||
processMin,
|
||||
processMax,
|
||||
{
|
||||
metricId,
|
||||
predictedTimestamps: predictedMeasurement.timestamps,
|
||||
measuredTimestamps: measuredMeasurement.timestamps,
|
||||
...(this.driftProfiles[metricId] || {}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_applyDriftPenalty(drift, confidence, flags, prefix) {
|
||||
if (!drift || !drift.valid || !Number.isFinite(drift.nrmse)) return confidence;
|
||||
if (drift.immediateLevel >= 3) {
|
||||
confidence -= 0.3;
|
||||
flags.push(`${prefix}_high_immediate_drift`);
|
||||
} else if (drift.immediateLevel === 2) {
|
||||
confidence -= 0.2;
|
||||
flags.push(`${prefix}_medium_immediate_drift`);
|
||||
} else if (drift.immediateLevel === 1) {
|
||||
confidence -= 0.1;
|
||||
flags.push(`${prefix}_low_immediate_drift`);
|
||||
}
|
||||
if (drift.longTermLevel >= 2) {
|
||||
confidence -= 0.1;
|
||||
flags.push(`${prefix}_long_term_drift`);
|
||||
}
|
||||
return confidence;
|
||||
}
|
||||
|
||||
_updatePredictionHealth() {
|
||||
const status = this.getPressureInitializationStatus();
|
||||
const pressureDrift = this._updatePressureDriftStatus();
|
||||
const flags = [...pressureDrift.flags];
|
||||
let confidence = 0;
|
||||
|
||||
const pressureSource = status.source;
|
||||
if (pressureSource === "differential") {
|
||||
confidence = 0.9;
|
||||
} else if (pressureSource === "upstream" || pressureSource === "downstream") {
|
||||
confidence = 0.55;
|
||||
} else {
|
||||
confidence = 0.2;
|
||||
}
|
||||
|
||||
if (!this._isOperationalState()) {
|
||||
confidence = 0;
|
||||
flags.push("not_operational");
|
||||
}
|
||||
|
||||
if (pressureDrift.level >= 3) confidence -= 0.35;
|
||||
else if (pressureDrift.level === 2) confidence -= 0.2;
|
||||
else if (pressureDrift.level === 1) confidence -= 0.1;
|
||||
|
||||
const currentPosition = Number(this.state?.getCurrentPosition?.());
|
||||
const { min, max } = this._resolveSetpointBounds();
|
||||
if (Number.isFinite(currentPosition) && Number.isFinite(min) && Number.isFinite(max) && max > min) {
|
||||
const span = max - min;
|
||||
const edgeDistance = Math.min(Math.abs(currentPosition - min), Math.abs(max - currentPosition));
|
||||
if (edgeDistance < span * 0.05) {
|
||||
confidence -= 0.1;
|
||||
flags.push("near_curve_edge");
|
||||
}
|
||||
}
|
||||
|
||||
confidence = this._applyDriftPenalty(this.flowDrift, confidence, flags, "flow");
|
||||
confidence = this._applyDriftPenalty(this.powerDrift, confidence, flags, "power");
|
||||
|
||||
confidence = Math.max(0, Math.min(1, confidence));
|
||||
let quality = "invalid";
|
||||
if (confidence >= 0.8) quality = "high";
|
||||
else if (confidence >= 0.55) quality = "medium";
|
||||
else if (confidence >= 0.3) quality = "low";
|
||||
|
||||
this.predictionHealth = {
|
||||
quality,
|
||||
confidence,
|
||||
pressureSource,
|
||||
flags: flags.length ? Array.from(new Set(flags)) : ["nominal"],
|
||||
};
|
||||
|
||||
return this.predictionHealth;
|
||||
}
|
||||
|
||||
reverseCurve(curve) {
|
||||
const reversedCurve = {};
|
||||
@@ -322,8 +758,15 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
return await this.executeSequence(parameter);
|
||||
|
||||
case "flowmovement":
|
||||
// External flow setpoint is interpreted in configured output flow unit.
|
||||
const canonicalFlowSetpoint = this._convertUnitValue(
|
||||
parameter,
|
||||
this.unitPolicy.output.flow,
|
||||
this.unitPolicy.canonical.flow,
|
||||
'flowmovement setpoint'
|
||||
);
|
||||
// Calculate the control value for a desired flow
|
||||
const pos = this.calcCtrl(parameter);
|
||||
const pos = this.calcCtrl(canonicalFlowSetpoint);
|
||||
// Move to the desired setpoint
|
||||
return await this.setpoint(pos);
|
||||
|
||||
@@ -399,42 +842,72 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
async setpoint(setpoint) {
|
||||
|
||||
try {
|
||||
// Validate setpoint
|
||||
if (typeof setpoint !== 'number' || setpoint < 0) {
|
||||
throw new Error("Invalid setpoint: Setpoint must be a non-negative number.");
|
||||
// Validate and normalize setpoint
|
||||
if (!Number.isFinite(setpoint)) {
|
||||
this.logger.error("Invalid setpoint: Setpoint must be a finite number.");
|
||||
return;
|
||||
}
|
||||
const { min, max } = this._resolveSetpointBounds();
|
||||
const constrainedSetpoint = Math.min(Math.max(setpoint, min), max);
|
||||
if (constrainedSetpoint !== setpoint) {
|
||||
this.logger.warn(`Requested setpoint ${setpoint} constrained to ${constrainedSetpoint} (min=${min}, max=${max})`);
|
||||
}
|
||||
|
||||
this.logger.info(`Setting setpoint to ${setpoint}. Current position: ${this.state.getCurrentPosition()}`);
|
||||
this.logger.info(`Setting setpoint to ${constrainedSetpoint}. Current position: ${this.state.getCurrentPosition()}`);
|
||||
|
||||
// Move to the desired setpoint
|
||||
await this.state.moveTo(setpoint);
|
||||
await this.state.moveTo(constrainedSetpoint);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error setting setpoint: ${error}`);
|
||||
this.logger.error(`Error setting setpoint: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
_resolveSetpointBounds() {
|
||||
const stateMin = Number(this.state?.movementManager?.minPosition);
|
||||
const stateMax = Number(this.state?.movementManager?.maxPosition);
|
||||
const curveMin = Number(this.predictFlow?.currentFxyXMin);
|
||||
const curveMax = Number(this.predictFlow?.currentFxyXMax);
|
||||
|
||||
const minCandidates = [stateMin, curveMin].filter(Number.isFinite);
|
||||
const maxCandidates = [stateMax, curveMax].filter(Number.isFinite);
|
||||
|
||||
const fallbackMin = Number.isFinite(stateMin) ? stateMin : 0;
|
||||
const fallbackMax = Number.isFinite(stateMax) ? stateMax : 100;
|
||||
|
||||
let min = minCandidates.length ? Math.max(...minCandidates) : fallbackMin;
|
||||
let max = maxCandidates.length ? Math.min(...maxCandidates) : fallbackMax;
|
||||
|
||||
if (min > max) {
|
||||
this.logger.warn(`Invalid setpoint bounds detected (min=${min}, max=${max}). Falling back to movement bounds.`);
|
||||
min = fallbackMin;
|
||||
max = fallbackMax;
|
||||
}
|
||||
|
||||
return { min, max };
|
||||
}
|
||||
|
||||
// Calculate flow based on current pressure and position
|
||||
calcFlow(x) {
|
||||
if(this.hasCurve) {
|
||||
if (!this._isOperationalState()) {
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(0,Date.now(),this.config.general.unit);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(0,Date.now(),this.config.general.unit);
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(0,Date.now(),this.unitPolicy.canonical.flow);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(0,Date.now(),this.unitPolicy.canonical.flow);
|
||||
this.logger.debug(`Machine is not operational. Setting predicted flow to 0.`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const cFlow = this.predictFlow.y(x);
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(cFlow,Date.now(),this.config.general.unit);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(cFlow,Date.now(),this.config.general.unit);
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(cFlow,Date.now(),this.unitPolicy.canonical.flow);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(cFlow,Date.now(),this.unitPolicy.canonical.flow);
|
||||
//this.logger.debug(`Calculated flow: ${cFlow} for pressure: ${this.getMeasuredPressure()} and position: ${x}`);
|
||||
return cFlow;
|
||||
}
|
||||
|
||||
// If no curve data is available, log a warning and return 0
|
||||
this.logger.warn(`No curve data available for flow calculation. Returning 0.`);
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(0, Date.now(),this.config.general.unit);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(0, Date.now(),this.config.general.unit);
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(0, Date.now(),this.unitPolicy.canonical.flow);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(0, Date.now(),this.unitPolicy.canonical.flow);
|
||||
return 0;
|
||||
|
||||
}
|
||||
@@ -443,20 +916,20 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
calcPower(x) {
|
||||
if(this.hasCurve) {
|
||||
if (!this._isOperationalState()) {
|
||||
this.measurements.type("power").variant("predicted").position('atEquipment').value(0);
|
||||
this.measurements.type("power").variant("predicted").position('atEquipment').value(0, Date.now(), this.unitPolicy.canonical.power);
|
||||
this.logger.debug(`Machine is not operational. Setting predicted power to 0.`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//this.predictPower.currentX = x; Decrepated
|
||||
const cPower = this.predictPower.y(x);
|
||||
this.measurements.type("power").variant("predicted").position('atEquipment').value(cPower);
|
||||
this.measurements.type("power").variant("predicted").position('atEquipment').value(cPower, Date.now(), this.unitPolicy.canonical.power);
|
||||
//this.logger.debug(`Calculated power: ${cPower} for pressure: ${this.getMeasuredPressure()} and position: ${x}`);
|
||||
return cPower;
|
||||
}
|
||||
// If no curve data is available, log a warning and return 0
|
||||
this.logger.warn(`No curve data available for power calculation. Returning 0.`);
|
||||
this.measurements.type("power").variant("predicted").position('atEquipment').value(0);
|
||||
this.measurements.type("power").variant("predicted").position('atEquipment').value(0, Date.now(), this.unitPolicy.canonical.power);
|
||||
return 0;
|
||||
|
||||
}
|
||||
@@ -474,7 +947,7 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
|
||||
// If no curve data is available, log a warning and return 0
|
||||
this.logger.warn(`No curve data available for power calculation. Returning 0.`);
|
||||
this.measurements.type("power").variant("predicted").position('atEquipment').value(0);
|
||||
this.measurements.type("power").variant("predicted").position('atEquipment').value(0, Date.now(), this.unitPolicy.canonical.power);
|
||||
return 0;
|
||||
|
||||
}
|
||||
@@ -491,7 +964,7 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
|
||||
// If no curve data is available, log a warning and return 0
|
||||
this.logger.warn(`No curve data available for control calculation. Returning 0.`);
|
||||
this.measurements.type("ctrl").variant("predicted").position('atEquipment').value(0);
|
||||
this.measurements.type("ctrl").variant("predicted").position('atEquipment').value(0, Date.now());
|
||||
return 0;
|
||||
|
||||
}
|
||||
@@ -567,8 +1040,8 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
//update the distance from peak
|
||||
this.calcDistanceBEP(efficiency,cog,minEfficiency);
|
||||
//place min and max flow capabilities in containerthis.predictFlow.currentFxyYMax - this.predictFlow.currentFxyYMin
|
||||
this.measurements.type('flow').variant('predicted').position('max').value(this.predictFlow.currentFxyYMax).unit(this.config.general.unit);
|
||||
this.measurements.type('flow').variant('predicted').position('min').value(this.predictFlow.currentFxyYMin).unit(this.config.general.unit);
|
||||
this.measurements.type('flow').variant('predicted').position('max').value(this.predictFlow.currentFxyYMax, Date.now(), this.unitPolicy.canonical.flow);
|
||||
this.measurements.type('flow').variant('predicted').position('min').value(this.predictFlow.currentFxyYMin, Date.now(), this.unitPolicy.canonical.flow);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -639,11 +1112,19 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
return;
|
||||
}
|
||||
|
||||
let measurementUnit;
|
||||
try {
|
||||
measurementUnit = this._resolveMeasurementUnit('pressure', context.unit);
|
||||
} catch (error) {
|
||||
this.logger.warn(`Rejected simulated pressure measurement: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
child.measurements
|
||||
.type("pressure")
|
||||
.variant("measured")
|
||||
.position(normalizedPosition)
|
||||
.value(value, context.timestamp || Date.now(), context.unit || "mbar");
|
||||
.value(value, context.timestamp || Date.now(), measurementUnit);
|
||||
}
|
||||
|
||||
handleMeasuredFlow() {
|
||||
@@ -702,19 +1183,36 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
|
||||
updateMeasuredTemperature(value, position, context = {}) {
|
||||
this.logger.debug(`Temperature update: ${value} at ${position} from ${context.childName || 'child'} (${context.childId || 'unknown-id'})`);
|
||||
let measurementUnit;
|
||||
try {
|
||||
measurementUnit = this._resolveMeasurementUnit('temperature', context.unit);
|
||||
} catch (error) {
|
||||
this.logger.warn(`Rejected temperature update: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
this.measurements.type("temperature").variant("measured").position(position || 'atEquipment').child(context.childId).value(value, context.timestamp, measurementUnit);
|
||||
}
|
||||
|
||||
// context handler for pressure updates
|
||||
updateMeasuredPressure(value, position, context = {}) {
|
||||
|
||||
this.logger.debug(`Pressure update: ${value} at ${position} from ${context.childName || 'child'} (${context.childId || 'unknown-id'})`);
|
||||
let measurementUnit;
|
||||
try {
|
||||
measurementUnit = this._resolveMeasurementUnit('pressure', context.unit);
|
||||
} catch (error) {
|
||||
this.logger.warn(`Rejected pressure update: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store in parent's measurement container
|
||||
this.measurements.type("pressure").variant("measured").position(position).value(value, context.timestamp, context.unit);
|
||||
this.measurements.type("pressure").variant("measured").position(position).child(context.childId).value(value, context.timestamp, measurementUnit);
|
||||
|
||||
// Determine what kind of value to use as pressure (upstream , downstream or difference)
|
||||
const pressure = this.getMeasuredPressure();
|
||||
this.updatePosition();
|
||||
this._updatePressureDriftStatus();
|
||||
this._updatePredictionHealth();
|
||||
|
||||
this.logger.debug(`Using pressure: ${pressure} for calculations`);
|
||||
}
|
||||
@@ -727,15 +1225,61 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
}
|
||||
|
||||
this.logger.debug(`Flow update: ${value} at ${position} from ${context.childName || 'child'}`);
|
||||
let measurementUnit;
|
||||
try {
|
||||
measurementUnit = this._resolveMeasurementUnit('flow', context.unit);
|
||||
} catch (error) {
|
||||
this.logger.warn(`Rejected flow update: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store in parent's measurement container
|
||||
this.measurements.type("flow").variant("measured").position(position).value(value, context.timestamp, context.unit);
|
||||
this.measurements.type("flow").variant("measured").position(position).child(context.childId).value(value, context.timestamp, measurementUnit);
|
||||
|
||||
// Update predicted flow if you have prediction capability
|
||||
if (this.predictFlow) {
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(this.predictFlow.outputY || 0);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(this.predictFlow.outputY || 0);
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(this.predictFlow.outputY || 0, Date.now(), this.unitPolicy.canonical.flow);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(this.predictFlow.outputY || 0, Date.now(), this.unitPolicy.canonical.flow);
|
||||
}
|
||||
|
||||
const measuredCanonical = this.measurements
|
||||
.type("flow")
|
||||
.variant("measured")
|
||||
.position(position)
|
||||
.getCurrentValue(this.unitPolicy.canonical.flow);
|
||||
|
||||
this._updateMetricDrift("flow", measuredCanonical, context);
|
||||
this._updatePredictionHealth();
|
||||
}
|
||||
|
||||
updateMeasuredPower(value, position, context = {}) {
|
||||
if (!this._isOperationalState()) {
|
||||
this.logger.warn(`Machine not operational, skipping power update from ${context.childName || 'unknown'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.debug(`Power update: ${value} at ${position} from ${context.childName || 'child'}`);
|
||||
let measurementUnit;
|
||||
try {
|
||||
measurementUnit = this._resolveMeasurementUnit('power', context.unit);
|
||||
} catch (error) {
|
||||
this.logger.warn(`Rejected power update: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
this.measurements.type("power").variant("measured").position(position).child(context.childId).value(value, context.timestamp, measurementUnit);
|
||||
|
||||
if (this.predictPower) {
|
||||
this.measurements.type("power").variant("predicted").position("atEquipment").value(this.predictPower.outputY || 0, Date.now(), this.unitPolicy.canonical.power);
|
||||
}
|
||||
|
||||
const measuredCanonical = this.measurements
|
||||
.type("power")
|
||||
.variant("measured")
|
||||
.position(position)
|
||||
.getCurrentValue(this.unitPolicy.canonical.power);
|
||||
|
||||
this._updateMetricDrift("power", measuredCanonical, context);
|
||||
this._updatePredictionHealth();
|
||||
}
|
||||
|
||||
// Helper method for operational state check
|
||||
@@ -767,6 +1311,8 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
|
||||
}
|
||||
|
||||
this._updatePredictionHealth();
|
||||
|
||||
}
|
||||
|
||||
calcDistanceFromPeak(currentEfficiency,peakEfficiency){
|
||||
@@ -889,9 +1435,17 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
this.measurements.type("efficiency").variant(variant).position('atEquipment').value(specificFlow);
|
||||
this.measurements.type("specificEnergyConsumption").variant(variant).position('atEquipment').value(specificEnergyConsumption);
|
||||
|
||||
if(pressureDiff?.value != null && flowM3s != null && powerWatt != null){
|
||||
const meterPerBar = pressureDiff.value / rho * g;
|
||||
const nHydraulicEfficiency = rho * g * flowM3s * (pressureDiff.value * meterPerBar ) / powerWatt;
|
||||
if (pressureDiff?.value != null && Number.isFinite(flowM3s) && Number.isFinite(powerWatt) && powerWatt > 0) {
|
||||
// Engineering references: P_h = Q * Δp = ρ g Q H, η_h = P_h / P_in
|
||||
const pressureDiffPa = Number(pressureDiff.value);
|
||||
const headMeters = (Number.isFinite(rho) && rho > 0) ? pressureDiffPa / (rho * g) : null;
|
||||
const hydraulicPowerW = pressureDiffPa * flowM3s;
|
||||
const nHydraulicEfficiency = hydraulicPowerW / powerWatt;
|
||||
|
||||
if (Number.isFinite(headMeters)) {
|
||||
this.measurements.type("pumpHead").variant(variant).position('atEquipment').value(headMeters, Date.now(), 'm');
|
||||
}
|
||||
this.measurements.type("hydraulicPower").variant(variant).position('atEquipment').value(hydraulicPowerW, Date.now(), 'W');
|
||||
this.measurements.type("nHydraulicEfficiency").variant(variant).position('atEquipment').value(nHydraulicEfficiency);
|
||||
}
|
||||
|
||||
@@ -904,7 +1458,13 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
|
||||
updateCurve(newCurve) {
|
||||
this.logger.info(`Updating machine curve`);
|
||||
const newConfig = { asset: { machineCurve: newCurve } };
|
||||
const normalizedCurve = this._normalizeMachineCurve(newCurve);
|
||||
const newConfig = {
|
||||
asset: {
|
||||
machineCurve: normalizedCurve,
|
||||
curveUnits: this.unitPolicy.curve,
|
||||
},
|
||||
};
|
||||
|
||||
//validate input of new curve fed to the machine
|
||||
this.config = this.configUtils.updateConfig(this.config, newConfig);
|
||||
@@ -945,7 +1505,9 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
|
||||
// Improved output object generation
|
||||
|
||||
const output = this.measurements.getFlattenedOutput();
|
||||
const output = this.measurements.getFlattenedOutput({
|
||||
requestedUnits: this.unitPolicy.output,
|
||||
});
|
||||
|
||||
//fill in the rest of the output object
|
||||
output["state"] = this.state.getCurrentState();
|
||||
@@ -962,10 +1524,30 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
const flowDrift = this.flowDrift;
|
||||
output["flowNrmse"] = flowDrift.nrmse;
|
||||
output["flowLongterNRMSD"] = flowDrift.longTermNRMSD;
|
||||
output["flowLongTermNRMSD"] = flowDrift.longTermNRMSD;
|
||||
output["flowImmediateLevel"] = flowDrift.immediateLevel;
|
||||
output["flowLongTermLevel"] = flowDrift.longTermLevel;
|
||||
output["flowDriftValid"] = flowDrift.valid;
|
||||
}
|
||||
|
||||
if(this.powerDrift != null){
|
||||
const powerDrift = this.powerDrift;
|
||||
output["powerNrmse"] = powerDrift.nrmse;
|
||||
output["powerLongTermNRMSD"] = powerDrift.longTermNRMSD;
|
||||
output["powerImmediateLevel"] = powerDrift.immediateLevel;
|
||||
output["powerLongTermLevel"] = powerDrift.longTermLevel;
|
||||
output["powerDriftValid"] = powerDrift.valid;
|
||||
}
|
||||
|
||||
output["pressureDriftLevel"] = this.pressureDrift.level;
|
||||
output["pressureDriftSource"] = this.pressureDrift.source;
|
||||
output["pressureDriftFlags"] = this.pressureDrift.flags;
|
||||
|
||||
output["predictionQuality"] = this.predictionHealth.quality;
|
||||
output["predictionConfidence"] = Math.round(this.predictionHealth.confidence * 1000) / 1000;
|
||||
output["predictionPressureSource"] = this.predictionHealth.pressureSource;
|
||||
output["predictionFlags"] = this.predictionHealth.flags;
|
||||
|
||||
//should this all go in the container of measurements?
|
||||
output["effDistFromPeak"] = this.absDistFromPeak;
|
||||
output["effRelDistFromPeak"] = this.relDistFromPeak;
|
||||
@@ -978,4 +1560,3 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
} // end of class
|
||||
|
||||
module.exports = Machine;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user