2025-06-10 12:36:39 +02:00
|
|
|
|
const MeasurementBuilder = require('./MeasurementBuilder');
|
2025-08-07 13:52:29 +02:00
|
|
|
|
const EventEmitter = require('events');
|
|
|
|
|
|
const convertModule = require('../convert/index');
|
2025-06-10 12:36:39 +02:00
|
|
|
|
|
|
|
|
|
|
class MeasurementContainer {
|
2025-10-05 09:34:00 +02:00
|
|
|
|
constructor(options = {},logger) {
|
2026-02-23 13:17:47 +01:00
|
|
|
|
this.logger = logger || null;
|
2025-08-07 13:52:29 +02:00
|
|
|
|
this.emitter = new EventEmitter();
|
2025-06-10 12:36:39 +02:00
|
|
|
|
this.measurements = {};
|
|
|
|
|
|
this.windowSize = options.windowSize || 10; // Default window size
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
2025-06-10 12:36:39 +02:00
|
|
|
|
// For chaining context
|
2025-11-28 09:59:39 +01:00
|
|
|
|
this._currentChildId = null;
|
2025-06-10 12:36:39 +02:00
|
|
|
|
this._currentType = null;
|
|
|
|
|
|
this._currentVariant = null;
|
|
|
|
|
|
this._currentPosition = null;
|
2025-10-05 09:34:00 +02:00
|
|
|
|
this._currentDistance = null;
|
2025-08-07 13:52:29 +02:00
|
|
|
|
this._unit = null;
|
|
|
|
|
|
|
2026-03-11 11:13:05 +01:00
|
|
|
|
// Default units for each measurement type (ingress/preferred)
|
2025-08-07 13:52:29 +02:00
|
|
|
|
this.defaultUnits = {
|
|
|
|
|
|
pressure: 'mbar',
|
|
|
|
|
|
flow: 'm3/h',
|
|
|
|
|
|
power: 'kW',
|
|
|
|
|
|
temperature: 'C',
|
|
|
|
|
|
volume: 'm3',
|
|
|
|
|
|
length: 'm',
|
|
|
|
|
|
...options.defaultUnits // Allow override
|
|
|
|
|
|
};
|
2026-03-11 11:13:05 +01:00
|
|
|
|
|
|
|
|
|
|
// Canonical storage unit map (single conversion anchor per measurement type)
|
|
|
|
|
|
this.canonicalUnits = {
|
|
|
|
|
|
pressure: 'Pa',
|
|
|
|
|
|
atmPressure: 'Pa',
|
|
|
|
|
|
flow: 'm3/s',
|
|
|
|
|
|
power: 'W',
|
|
|
|
|
|
hydraulicPower: 'W',
|
|
|
|
|
|
temperature: 'K',
|
|
|
|
|
|
volume: 'm3',
|
|
|
|
|
|
length: 'm',
|
|
|
|
|
|
mass: 'kg',
|
|
|
|
|
|
energy: 'J',
|
|
|
|
|
|
...options.canonicalUnits,
|
|
|
|
|
|
};
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
2025-08-07 13:52:29 +02:00
|
|
|
|
// Auto-conversion settings
|
|
|
|
|
|
this.autoConvert = options.autoConvert !== false; // Default to true
|
|
|
|
|
|
this.preferredUnits = options.preferredUnits || {}; // Per-measurement overrides
|
2026-03-11 11:13:05 +01:00
|
|
|
|
this.storeCanonical = options.storeCanonical === true;
|
|
|
|
|
|
this.strictUnitValidation = options.strictUnitValidation === true;
|
|
|
|
|
|
this.throwOnInvalidUnit = options.throwOnInvalidUnit === true;
|
|
|
|
|
|
this.requireUnitForTypes = new Set(
|
|
|
|
|
|
(options.requireUnitForTypes || []).map((t) => String(t).trim().toLowerCase())
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Map EVOLV measurement types to convert-module measure families
|
|
|
|
|
|
this.measureMap = {
|
|
|
|
|
|
pressure: 'pressure',
|
|
|
|
|
|
atmpressure: 'pressure',
|
|
|
|
|
|
flow: 'volumeFlowRate',
|
|
|
|
|
|
power: 'power',
|
|
|
|
|
|
hydraulicpower: 'power',
|
|
|
|
|
|
reactivepower: 'reactivePower',
|
|
|
|
|
|
apparentpower: 'apparentPower',
|
|
|
|
|
|
temperature: 'temperature',
|
|
|
|
|
|
volume: 'volume',
|
|
|
|
|
|
length: 'length',
|
|
|
|
|
|
mass: 'mass',
|
|
|
|
|
|
energy: 'energy',
|
|
|
|
|
|
reactiveenergy: 'reactiveEnergy',
|
|
|
|
|
|
};
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
2025-08-07 13:52:29 +02:00
|
|
|
|
// For chaining context
|
|
|
|
|
|
this._currentType = null;
|
|
|
|
|
|
this._currentVariant = null;
|
|
|
|
|
|
this._currentPosition = null;
|
|
|
|
|
|
this._unit = null;
|
|
|
|
|
|
|
|
|
|
|
|
// NEW: Enhanced child identification
|
|
|
|
|
|
this.childId = null;
|
|
|
|
|
|
this.childName = null;
|
|
|
|
|
|
this.parentRef = null;
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
2025-08-07 13:52:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NEW: Methods to set child context
|
|
|
|
|
|
setChildId(childId) {
|
2025-10-05 09:34:00 +02:00
|
|
|
|
this.childId = childId;
|
|
|
|
|
|
return this;
|
2025-08-07 13:52:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-28 09:59:39 +01:00
|
|
|
|
child(childId) {
|
|
|
|
|
|
this._currentChildId = childId || 'default';
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 13:52:29 +02:00
|
|
|
|
setChildName(childName) {
|
2025-10-05 09:34:00 +02:00
|
|
|
|
this.childName = childName;
|
|
|
|
|
|
return this;
|
2025-08-07 13:52:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setParentRef(parent) {
|
2025-10-05 09:34:00 +02:00
|
|
|
|
this.parentRef = parent;
|
|
|
|
|
|
return this;
|
2025-08-07 13:52:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// New method to set preferred units
|
|
|
|
|
|
setPreferredUnit(measurementType, unit) {
|
|
|
|
|
|
this.preferredUnits[measurementType] = unit;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 11:13:05 +01:00
|
|
|
|
setCanonicalUnit(measurementType, unit) {
|
|
|
|
|
|
this.canonicalUnits[measurementType] = unit;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 13:52:29 +02:00
|
|
|
|
// Get the target unit for a measurement type
|
|
|
|
|
|
_getTargetUnit(measurementType) {
|
2025-10-05 09:34:00 +02:00
|
|
|
|
return this.preferredUnits[measurementType] ||
|
|
|
|
|
|
this.defaultUnits[measurementType] ||
|
|
|
|
|
|
null;
|
2025-06-10 12:36:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 11:13:05 +01:00
|
|
|
|
_getCanonicalUnit(measurementType) {
|
|
|
|
|
|
return this.canonicalUnits[measurementType] || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_normalizeType(measurementType) {
|
|
|
|
|
|
return String(measurementType || '').trim().toLowerCase();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_describeUnit(unit) {
|
|
|
|
|
|
if (typeof unit !== 'string' || unit.trim() === '') return null;
|
|
|
|
|
|
try {
|
|
|
|
|
|
return convertModule().describe(unit.trim());
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isUnitCompatible(measurementType, unit) {
|
|
|
|
|
|
const desc = this._describeUnit(unit);
|
|
|
|
|
|
if (!desc) return false;
|
|
|
|
|
|
const normalizedType = this._normalizeType(measurementType);
|
|
|
|
|
|
const expectedMeasure = this.measureMap[normalizedType];
|
|
|
|
|
|
if (!expectedMeasure) return true;
|
|
|
|
|
|
return desc.measure === expectedMeasure;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_handleUnitViolation(message) {
|
|
|
|
|
|
if (this.throwOnInvalidUnit) {
|
|
|
|
|
|
throw new Error(message);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.warn(message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_resolveUnitPolicy(measurementType, sourceUnit = null) {
|
|
|
|
|
|
const normalizedType = this._normalizeType(measurementType);
|
|
|
|
|
|
const rawSourceUnit = typeof sourceUnit === 'string' && sourceUnit.trim()
|
|
|
|
|
|
? sourceUnit.trim()
|
|
|
|
|
|
: null;
|
|
|
|
|
|
const fallbackIngressUnit = this._getTargetUnit(measurementType);
|
|
|
|
|
|
const canonicalUnit = this._getCanonicalUnit(measurementType);
|
|
|
|
|
|
const resolvedSourceUnit = rawSourceUnit || fallbackIngressUnit || canonicalUnit || null;
|
|
|
|
|
|
|
|
|
|
|
|
if (this.requireUnitForTypes.has(normalizedType) && !rawSourceUnit) {
|
|
|
|
|
|
this._handleUnitViolation(`Missing source unit for required measurement type '${measurementType}'.`);
|
|
|
|
|
|
return { valid: false };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (resolvedSourceUnit && !this.isUnitCompatible(measurementType, resolvedSourceUnit)) {
|
|
|
|
|
|
this._handleUnitViolation(`Incompatible or unknown source unit '${resolvedSourceUnit}' for measurement type '${measurementType}'.`);
|
|
|
|
|
|
return { valid: false };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const resolvedStorageUnit = this.storeCanonical
|
|
|
|
|
|
? (canonicalUnit || fallbackIngressUnit || resolvedSourceUnit)
|
|
|
|
|
|
: (fallbackIngressUnit || canonicalUnit || resolvedSourceUnit);
|
|
|
|
|
|
|
|
|
|
|
|
if (resolvedStorageUnit && !this.isUnitCompatible(measurementType, resolvedStorageUnit)) {
|
|
|
|
|
|
this._handleUnitViolation(`Incompatible storage unit '${resolvedStorageUnit}' for measurement type '${measurementType}'.`);
|
|
|
|
|
|
return { valid: false };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
valid: true,
|
|
|
|
|
|
sourceUnit: resolvedSourceUnit,
|
|
|
|
|
|
storageUnit: resolvedStorageUnit || null,
|
|
|
|
|
|
strictValidation: this.strictUnitValidation,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-28 09:59:39 +01:00
|
|
|
|
getUnit(type) {
|
|
|
|
|
|
if (!type) return null;
|
|
|
|
|
|
if (this.preferredUnits && this.preferredUnits[type]) return this.preferredUnits[type];
|
|
|
|
|
|
if (this.defaultUnits && this.defaultUnits[type]) return this.defaultUnits[type];
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-10 12:36:39 +02:00
|
|
|
|
// Chainable methods
|
|
|
|
|
|
type(typeName) {
|
|
|
|
|
|
this._currentType = typeName;
|
|
|
|
|
|
this._currentVariant = null;
|
|
|
|
|
|
this._currentPosition = null;
|
2025-11-30 09:24:29 +01:00
|
|
|
|
this._currentChildId = null;
|
2025-06-10 12:36:39 +02:00
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
variant(variantName) {
|
|
|
|
|
|
if (!this._currentType) {
|
2026-02-23 13:17:47 +01:00
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.warn('variant() ignored: type must be specified before variant');
|
|
|
|
|
|
}
|
|
|
|
|
|
return this;
|
2025-06-10 12:36:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
this._currentVariant = variantName;
|
|
|
|
|
|
this._currentPosition = null;
|
2025-11-30 17:45:45 +01:00
|
|
|
|
this._currentChildId = null;
|
2025-06-10 12:36:39 +02:00
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-17 13:21:35 +02:00
|
|
|
|
position(positionValue) {
|
2025-06-10 12:36:39 +02:00
|
|
|
|
if (!this._currentVariant) {
|
2026-02-23 13:17:47 +01:00
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.warn('position() ignored: variant must be specified before position');
|
|
|
|
|
|
}
|
|
|
|
|
|
return this;
|
2025-06-10 12:36:39 +02:00
|
|
|
|
}
|
2025-09-17 14:52:25 +02:00
|
|
|
|
|
2026-02-23 13:17:47 +01:00
|
|
|
|
this._currentPosition = positionValue.toString().toLowerCase();
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
distance(distance) {
|
|
|
|
|
|
// If distance is not provided, derive from positionVsParent
|
|
|
|
|
|
if(distance === null) {
|
|
|
|
|
|
distance = this._convertPositionStr2Num(this._currentPosition);
|
2025-09-17 14:52:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 09:34:00 +02:00
|
|
|
|
this._currentDistance = distance;
|
2025-10-03 15:37:08 +02:00
|
|
|
|
|
2025-06-10 12:36:39 +02:00
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 13:52:29 +02:00
|
|
|
|
// ENHANCED: Update your existing value method
|
|
|
|
|
|
value(val, timestamp = Date.now(), sourceUnit = null) {
|
2025-10-05 09:34:00 +02:00
|
|
|
|
if (!this._ensureChainIsValid()) return this;
|
2026-03-11 11:13:05 +01:00
|
|
|
|
|
|
|
|
|
|
const unitPolicy = this._resolveUnitPolicy(this._currentType, sourceUnit);
|
|
|
|
|
|
if (!unitPolicy.valid) return this;
|
|
|
|
|
|
|
2025-10-05 09:34:00 +02:00
|
|
|
|
const measurement = this._getOrCreateMeasurement();
|
2026-03-11 11:13:05 +01:00
|
|
|
|
const targetUnit = unitPolicy.storageUnit;
|
|
|
|
|
|
|
2025-10-05 09:34:00 +02:00
|
|
|
|
let convertedValue = val;
|
2026-03-11 11:13:05 +01:00
|
|
|
|
let finalUnit = targetUnit || unitPolicy.sourceUnit;
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
|
|
|
|
|
// Auto-convert if enabled and units are specified
|
2026-03-11 11:13:05 +01:00
|
|
|
|
if (this.autoConvert && unitPolicy.sourceUnit && targetUnit && unitPolicy.sourceUnit !== targetUnit) {
|
2025-10-05 09:34:00 +02:00
|
|
|
|
try {
|
2026-03-11 11:13:05 +01:00
|
|
|
|
convertedValue = convertModule(val).from(unitPolicy.sourceUnit).to(targetUnit);
|
2025-10-05 09:34:00 +02:00
|
|
|
|
finalUnit = targetUnit;
|
|
|
|
|
|
|
|
|
|
|
|
if (this.logger) {
|
2026-03-11 11:13:05 +01:00
|
|
|
|
this.logger.debug(`Auto-converted ${val} ${unitPolicy.sourceUnit} to ${convertedValue} ${targetUnit}`);
|
2025-10-05 09:34:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2026-03-11 11:13:05 +01:00
|
|
|
|
const message = `Auto-conversion failed from ${unitPolicy.sourceUnit} to ${targetUnit}: ${error.message}`;
|
|
|
|
|
|
if (this.strictUnitValidation) {
|
|
|
|
|
|
this._handleUnitViolation(message);
|
|
|
|
|
|
return this;
|
2025-10-05 09:34:00 +02:00
|
|
|
|
}
|
2026-03-11 11:13:05 +01:00
|
|
|
|
if (this.logger) this.logger.warn(message);
|
2025-10-05 09:34:00 +02:00
|
|
|
|
convertedValue = val;
|
2026-03-11 11:13:05 +01:00
|
|
|
|
finalUnit = unitPolicy.sourceUnit;
|
2025-10-03 15:37:08 +02:00
|
|
|
|
}
|
2025-08-07 13:52:29 +02:00
|
|
|
|
}
|
2025-10-03 15:37:08 +02:00
|
|
|
|
|
2025-10-05 09:34:00 +02:00
|
|
|
|
measurement.setValue(convertedValue, timestamp);
|
|
|
|
|
|
|
2026-03-11 11:13:05 +01:00
|
|
|
|
if (finalUnit) {
|
2025-10-05 09:34:00 +02:00
|
|
|
|
measurement.setUnit(finalUnit);
|
|
|
|
|
|
}
|
2025-10-03 15:37:08 +02:00
|
|
|
|
|
2025-10-05 09:34:00 +02:00
|
|
|
|
// ENHANCED: Emit event with rich context
|
|
|
|
|
|
const eventData = {
|
|
|
|
|
|
value: convertedValue,
|
|
|
|
|
|
originalValue: val,
|
|
|
|
|
|
unit: finalUnit,
|
2026-03-11 11:13:05 +01:00
|
|
|
|
sourceUnit: unitPolicy.sourceUnit,
|
2025-10-05 09:34:00 +02:00
|
|
|
|
timestamp,
|
|
|
|
|
|
position: this._currentPosition,
|
|
|
|
|
|
distance: this._currentDistance,
|
|
|
|
|
|
variant: this._currentVariant,
|
|
|
|
|
|
type: this._currentType,
|
|
|
|
|
|
// NEW: Enhanced context
|
|
|
|
|
|
childId: this.childId,
|
|
|
|
|
|
childName: this.childName,
|
|
|
|
|
|
parentRef: this.parentRef,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Emit the exact event your parent expects
|
|
|
|
|
|
this.emitter.emit(`${this._currentType}.${this._currentVariant}.${this._currentPosition}`, eventData);
|
2025-10-14 13:51:57 +02:00
|
|
|
|
//console.log(`Emitted event: ${this._currentType}.${this._currentVariant}.${this._currentPosition}`, eventData);
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
|
|
|
|
|
return this;
|
2025-06-10 12:36:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-23 09:51:27 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Check whether a measurement series exists.
|
|
|
|
|
|
*
|
|
|
|
|
|
* You can rely on the current chain (type/variant/position already set via
|
|
|
|
|
|
* type().variant().position()), or pass them explicitly via the options.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {object} options
|
|
|
|
|
|
* @param {string} [options.type] Override the current type
|
|
|
|
|
|
* @param {string} [options.variant] Override the current variant
|
|
|
|
|
|
* @param {string} [options.position] Override the current position
|
|
|
|
|
|
* @param {boolean} [options.requireValues=false]
|
|
|
|
|
|
* When true, the series must contain at least one stored value.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
|
*/
|
|
|
|
|
|
exists({ type, variant, position, requireValues = false } = {}) {
|
|
|
|
|
|
const typeKey = type ?? this._currentType;
|
|
|
|
|
|
if (!typeKey) return false;
|
|
|
|
|
|
|
|
|
|
|
|
const variantKey = variant ?? this._currentVariant;
|
|
|
|
|
|
if (!variantKey) return false;
|
|
|
|
|
|
|
|
|
|
|
|
const positionKey = position ?? this._currentPosition;
|
|
|
|
|
|
|
|
|
|
|
|
const typeBucket = this.measurements[typeKey];
|
|
|
|
|
|
if (!typeBucket) return false;
|
|
|
|
|
|
|
|
|
|
|
|
const variantBucket = typeBucket[variantKey];
|
|
|
|
|
|
if (!variantBucket) return false;
|
|
|
|
|
|
|
|
|
|
|
|
if (!positionKey) {
|
|
|
|
|
|
// No specific position requested – just check the variant bucket.
|
|
|
|
|
|
return requireValues
|
|
|
|
|
|
? Object.values(variantBucket).some(m => m?.values?.length > 0)
|
|
|
|
|
|
: Object.keys(variantBucket).length > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const measurement = variantBucket[positionKey];
|
|
|
|
|
|
if (!measurement) return false;
|
|
|
|
|
|
|
|
|
|
|
|
return requireValues ? measurement.values?.length > 0 : true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-10 12:36:39 +02:00
|
|
|
|
unit(unitName) {
|
|
|
|
|
|
if (!this._ensureChainIsValid()) return this;
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
2025-06-10 12:36:39 +02:00
|
|
|
|
const measurement = this._getOrCreateMeasurement();
|
|
|
|
|
|
measurement.setUnit(unitName);
|
2025-08-07 13:52:29 +02:00
|
|
|
|
this._unit = unitName;
|
2025-06-10 12:36:39 +02:00
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Terminal operations - get data out
|
2025-11-28 09:59:39 +01:00
|
|
|
|
get() {
|
|
|
|
|
|
if (!this._ensureChainIsValid()) return null;
|
|
|
|
|
|
const variantBucket = this.measurements[this._currentType]?.[this._currentVariant];
|
|
|
|
|
|
if (!variantBucket) return null;
|
|
|
|
|
|
const posBucket = variantBucket[this._currentPosition];
|
|
|
|
|
|
if (!posBucket) return null;
|
|
|
|
|
|
|
|
|
|
|
|
// Legacy single measurement
|
|
|
|
|
|
if (posBucket?.getCurrentValue) return posBucket;
|
|
|
|
|
|
|
|
|
|
|
|
// Child-aware: pick requested child, otherwise fall back to default, otherwise first available
|
|
|
|
|
|
if (posBucket && typeof posBucket === 'object') {
|
|
|
|
|
|
const requestedKey = this._currentChildId || this.childId;
|
|
|
|
|
|
const keys = Object.keys(posBucket);
|
|
|
|
|
|
if (!keys.length) return null;
|
|
|
|
|
|
const measurement =
|
|
|
|
|
|
(requestedKey && posBucket[requestedKey]) ||
|
|
|
|
|
|
posBucket.default ||
|
|
|
|
|
|
posBucket[keys[0]];
|
|
|
|
|
|
return measurement || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-06-10 12:36:39 +02:00
|
|
|
|
|
2025-08-07 13:52:29 +02:00
|
|
|
|
getCurrentValue(requestedUnit = null) {
|
2025-06-10 12:36:39 +02:00
|
|
|
|
const measurement = this.get();
|
2025-08-07 13:52:29 +02:00
|
|
|
|
if (!measurement) return null;
|
|
|
|
|
|
const value = measurement.getCurrentValue();
|
|
|
|
|
|
if (value === null) return null;
|
2025-11-28 09:59:39 +01:00
|
|
|
|
if (!requestedUnit || !measurement.unit || requestedUnit === measurement.unit) {
|
2025-08-07 13:52:29 +02:00
|
|
|
|
return value;
|
|
|
|
|
|
}
|
2025-11-28 09:59:39 +01:00
|
|
|
|
try {
|
|
|
|
|
|
return convertModule(value).from(measurement.unit).to(requestedUnit);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (this.logger) this.logger.error(`Unit conversion failed: ${error.message}`);
|
|
|
|
|
|
return value;
|
2025-08-07 13:52:29 +02:00
|
|
|
|
}
|
2025-06-10 12:36:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 13:52:29 +02:00
|
|
|
|
getAverage(requestedUnit = null) {
|
2025-06-10 12:36:39 +02:00
|
|
|
|
const measurement = this.get();
|
2025-08-07 13:52:29 +02:00
|
|
|
|
if (!measurement) return null;
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
2025-08-07 13:52:29 +02:00
|
|
|
|
const avgValue = measurement.getAverage();
|
|
|
|
|
|
if (avgValue === null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
if (!requestedUnit || !measurement.unit || requestedUnit === measurement.unit) {
|
|
|
|
|
|
return avgValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
return convertModule(avgValue).from(measurement.unit).to(requestedUnit);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.error(`Unit conversion failed: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return avgValue;
|
|
|
|
|
|
}
|
2025-06-10 12:36:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getMin() {
|
|
|
|
|
|
const measurement = this.get();
|
|
|
|
|
|
return measurement ? measurement.getMin() : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getMax() {
|
|
|
|
|
|
const measurement = this.get();
|
|
|
|
|
|
return measurement ? measurement.getMax() : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getAllValues() {
|
|
|
|
|
|
const measurement = this.get();
|
|
|
|
|
|
return measurement ? measurement.getAllValues() : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 14:37:42 +02:00
|
|
|
|
getLaggedValue(lag = 1,requestedUnit = null ){
|
|
|
|
|
|
const measurement = this.get();
|
|
|
|
|
|
if (!measurement) return null;
|
|
|
|
|
|
|
2025-10-23 09:51:27 +02:00
|
|
|
|
let sample = measurement.getLaggedSample(lag);
|
|
|
|
|
|
if (sample === null) return null;
|
|
|
|
|
|
const value = sample.value;
|
2025-08-07 13:52:29 +02:00
|
|
|
|
|
2025-10-16 14:37:42 +02:00
|
|
|
|
// Return as-is if no unit conversion requested
|
|
|
|
|
|
if (!requestedUnit) {
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Convert if needed
|
|
|
|
|
|
if (measurement.unit && requestedUnit !== measurement.unit) {
|
|
|
|
|
|
try {
|
2026-02-12 10:14:56 +01:00
|
|
|
|
const convertedValue = convertModule(sample.value).from(measurement.unit).to(requestedUnit);
|
2025-10-23 09:51:27 +02:00
|
|
|
|
//replace old value in sample and return obj
|
|
|
|
|
|
sample.value = convertedValue ;
|
|
|
|
|
|
sample.unit = requestedUnit;
|
|
|
|
|
|
return sample;
|
|
|
|
|
|
|
2025-10-16 14:37:42 +02:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.error(`Unit conversion failed: ${error.message}`);
|
|
|
|
|
|
}
|
2025-10-23 09:51:27 +02:00
|
|
|
|
return sample; // Return original value if conversion fails
|
2025-10-16 14:37:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-15 14:09:37 +02:00
|
|
|
|
|
2025-10-16 14:37:42 +02:00
|
|
|
|
return value;
|
2025-10-15 14:09:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 15:22:51 +01:00
|
|
|
|
getLaggedSample(lag = 1,requestedUnit = null ){
|
|
|
|
|
|
const measurement = this.get();
|
|
|
|
|
|
if (!measurement) return null;
|
|
|
|
|
|
|
|
|
|
|
|
let sample = measurement.getLaggedSample(lag);
|
|
|
|
|
|
if (sample === null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
// Return as-is if no unit conversion requested
|
|
|
|
|
|
if (!requestedUnit) {
|
|
|
|
|
|
return sample;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Convert if needed
|
|
|
|
|
|
if (measurement.unit && requestedUnit !== measurement.unit) {
|
|
|
|
|
|
try {
|
2026-02-12 10:14:56 +01:00
|
|
|
|
const convertedValue = convertModule(sample.value).from(measurement.unit).to(requestedUnit);
|
2025-11-03 15:22:51 +01:00
|
|
|
|
//replace old value in sample and return obj
|
|
|
|
|
|
sample.value = convertedValue ;
|
|
|
|
|
|
sample.unit = requestedUnit;
|
|
|
|
|
|
return sample;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.error(`Unit conversion failed: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return sample; // Return original value if conversion fails
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return sample;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-28 09:59:39 +01:00
|
|
|
|
sum(type, variant, positions = [], targetUnit = null) {
|
|
|
|
|
|
const bucket = this.measurements?.[type]?.[variant];
|
|
|
|
|
|
if (!bucket) return 0;
|
|
|
|
|
|
return positions
|
|
|
|
|
|
.map((pos) => {
|
|
|
|
|
|
const posBucket = bucket[pos];
|
|
|
|
|
|
if (!posBucket) return 0;
|
|
|
|
|
|
return Object.values(posBucket)
|
|
|
|
|
|
.map((m) => {
|
|
|
|
|
|
if (!m?.getCurrentValue) return 0;
|
|
|
|
|
|
const val = m.getCurrentValue();
|
|
|
|
|
|
if (val == null) return 0;
|
|
|
|
|
|
const fromUnit = m.unit || targetUnit;
|
|
|
|
|
|
if (!targetUnit || !fromUnit || fromUnit === targetUnit) return val;
|
|
|
|
|
|
try { return convertModule(val).from(fromUnit).to(targetUnit); } catch { return val; }
|
|
|
|
|
|
})
|
|
|
|
|
|
.reduce((acc, v) => acc + (Number.isFinite(v) ? v : 0), 0);
|
|
|
|
|
|
})
|
|
|
|
|
|
.reduce((acc, v) => acc + v, 0);
|
|
|
|
|
|
}
|
2025-08-07 13:52:29 +02:00
|
|
|
|
|
2026-03-11 11:13:05 +01:00
|
|
|
|
getFlattenedOutput(options = {}) {
|
|
|
|
|
|
const requestedUnits = options.requestedUnits || (options.usePreferredUnits ? this.preferredUnits : null);
|
2025-11-28 09:59:39 +01:00
|
|
|
|
const out = {};
|
|
|
|
|
|
Object.entries(this.measurements).forEach(([type, variants]) => {
|
|
|
|
|
|
Object.entries(variants).forEach(([variant, positions]) => {
|
|
|
|
|
|
Object.entries(positions).forEach(([position, entry]) => {
|
|
|
|
|
|
// Legacy single series
|
|
|
|
|
|
if (entry?.getCurrentValue) {
|
2026-03-11 11:13:05 +01:00
|
|
|
|
out[`${type}.${variant}.${position}`] = this._resolveOutputValue(type, entry, requestedUnits);
|
2025-11-28 09:59:39 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Child-bucketed series
|
|
|
|
|
|
if (entry && typeof entry === 'object') {
|
|
|
|
|
|
Object.entries(entry).forEach(([childId, m]) => {
|
|
|
|
|
|
if (m?.getCurrentValue) {
|
2026-03-11 11:13:05 +01:00
|
|
|
|
out[`${type}.${variant}.${position}.${childId}`] = this._resolveOutputValue(type, m, requestedUnits);
|
2025-11-28 09:59:39 +01:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
return out;
|
2025-10-15 14:09:37 +02:00
|
|
|
|
}
|
2025-06-10 12:36:39 +02:00
|
|
|
|
|
2025-11-28 09:59:39 +01:00
|
|
|
|
// Difference calculations between positions
|
|
|
|
|
|
difference({ from = "downstream", to = "upstream", unit: requestedUnit } = {}) {
|
|
|
|
|
|
if (!this._currentType || !this._currentVariant) {
|
2026-02-23 13:17:47 +01:00
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.warn('difference() ignored: type and variant must be specified');
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
2025-11-28 09:59:39 +01:00
|
|
|
|
}
|
2025-10-15 14:09:37 +02:00
|
|
|
|
|
2025-11-28 09:59:39 +01:00
|
|
|
|
const get = pos => {
|
|
|
|
|
|
const bucket = this.measurements?.[this._currentType]?.[this._currentVariant]?.[pos];
|
|
|
|
|
|
if (!bucket) return null;
|
|
|
|
|
|
// child-aware bucket: pick current childId/default or first available
|
|
|
|
|
|
if (bucket && typeof bucket === 'object' && !bucket.getCurrentValue) {
|
|
|
|
|
|
const childKey = this._currentChildId || this.childId || Object.keys(bucket)[0];
|
|
|
|
|
|
return bucket?.[childKey] || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
// legacy single measurement
|
|
|
|
|
|
return bucket;
|
|
|
|
|
|
};
|
2025-06-10 12:36:39 +02:00
|
|
|
|
|
2025-11-28 09:59:39 +01:00
|
|
|
|
const a = get(from);
|
|
|
|
|
|
const b = get(to);
|
|
|
|
|
|
if (!a || !b || !a.values || !b.values || a.values.length === 0 || b.values.length === 0) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const targetUnit = requestedUnit || a.unit || b.unit;
|
|
|
|
|
|
const aVal = this._convertValueToUnit(a.getCurrentValue(), a.unit, targetUnit);
|
|
|
|
|
|
const bVal = this._convertValueToUnit(b.getCurrentValue(), b.unit, targetUnit);
|
2025-10-15 14:09:37 +02:00
|
|
|
|
|
2025-11-28 09:59:39 +01:00
|
|
|
|
const aAvg = this._convertValueToUnit(a.getAverage(), a.unit, targetUnit);
|
|
|
|
|
|
const bAvg = this._convertValueToUnit(b.getAverage(), b.unit, targetUnit);
|
2025-10-15 14:09:37 +02:00
|
|
|
|
|
2025-11-28 09:59:39 +01:00
|
|
|
|
return { value: aVal - bVal, avgDiff: aAvg - bAvg, unit: targetUnit, from, to };
|
|
|
|
|
|
}
|
2025-10-15 14:09:37 +02:00
|
|
|
|
|
2025-06-10 12:36:39 +02:00
|
|
|
|
// Helper methods
|
|
|
|
|
|
_ensureChainIsValid() {
|
|
|
|
|
|
if (!this._currentType || !this._currentVariant || !this._currentPosition) {
|
|
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.error('Incomplete measurement chain, required: type, variant, and position');
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_getOrCreateMeasurement() {
|
|
|
|
|
|
// Initialize nested structure if needed
|
|
|
|
|
|
if (!this.measurements[this._currentType]) {
|
|
|
|
|
|
this.measurements[this._currentType] = {};
|
|
|
|
|
|
}
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
2025-06-10 12:36:39 +02:00
|
|
|
|
if (!this.measurements[this._currentType][this._currentVariant]) {
|
|
|
|
|
|
this.measurements[this._currentType][this._currentVariant] = {};
|
|
|
|
|
|
}
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
2025-11-28 09:59:39 +01:00
|
|
|
|
const positionKey = this._currentPosition;
|
|
|
|
|
|
const childKey = this._currentChildId || this.childId || 'default';
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.measurements[this._currentType][this._currentVariant][positionKey]) {
|
|
|
|
|
|
this.measurements[this._currentType][this._currentVariant][positionKey] = {};
|
2025-06-10 12:36:39 +02:00
|
|
|
|
}
|
2025-11-28 09:59:39 +01:00
|
|
|
|
|
|
|
|
|
|
const bucket = this.measurements[this._currentType][this._currentVariant][positionKey];
|
|
|
|
|
|
|
|
|
|
|
|
if (!bucket[childKey]) {
|
|
|
|
|
|
bucket[childKey] = new MeasurementBuilder()
|
|
|
|
|
|
.setType(this._currentType)
|
|
|
|
|
|
.setVariant(this._currentVariant)
|
|
|
|
|
|
.setPosition(positionKey)
|
|
|
|
|
|
.setWindowSize(this.windowSize)
|
|
|
|
|
|
.setDistance(this._currentDistance)
|
|
|
|
|
|
.build();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return bucket[childKey];
|
2025-06-10 12:36:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Additional utility methods
|
|
|
|
|
|
getTypes() {
|
|
|
|
|
|
return Object.keys(this.measurements);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getVariants() {
|
|
|
|
|
|
if (!this._currentType) {
|
2026-02-23 13:17:47 +01:00
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.warn('getVariants() ignored: type must be specified first');
|
|
|
|
|
|
}
|
|
|
|
|
|
return [];
|
2025-06-10 12:36:39 +02:00
|
|
|
|
}
|
2025-10-05 09:34:00 +02:00
|
|
|
|
return this.measurements[this._currentType] ?
|
2025-06-10 12:36:39 +02:00
|
|
|
|
Object.keys(this.measurements[this._currentType]) : [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 11:13:05 +01:00
|
|
|
|
_resolveOutputValue(type, measurement, requestedUnits = null) {
|
|
|
|
|
|
const value = measurement.getCurrentValue();
|
|
|
|
|
|
if (!requestedUnits || value === null || typeof value === 'undefined') {
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
const targetUnit = requestedUnits[type];
|
|
|
|
|
|
if (!targetUnit) {
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
return this._convertValueToUnit(value, measurement.unit, targetUnit);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 09:34:00 +02:00
|
|
|
|
getPositions() {
|
2025-06-10 12:36:39 +02:00
|
|
|
|
if (!this._currentType || !this._currentVariant) {
|
2026-02-23 13:17:47 +01:00
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.warn('getPositions() ignored: type and variant must be specified first');
|
|
|
|
|
|
}
|
|
|
|
|
|
return [];
|
2025-06-10 12:36:39 +02:00
|
|
|
|
}
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
|
|
|
|
|
if (!this.measurements[this._currentType] ||
|
|
|
|
|
|
!this.measurements[this._currentType][this._currentVariant]) {
|
2025-06-10 12:36:39 +02:00
|
|
|
|
return [];
|
|
|
|
|
|
}
|
2025-09-23 14:17:42 +02:00
|
|
|
|
|
2025-10-05 09:34:00 +02:00
|
|
|
|
return Object.keys(this.measurements[this._currentType][this._currentVariant]);
|
2025-06-10 12:36:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
clear() {
|
|
|
|
|
|
this.measurements = {};
|
|
|
|
|
|
this._currentType = null;
|
|
|
|
|
|
this._currentVariant = null;
|
|
|
|
|
|
this._currentPosition = null;
|
|
|
|
|
|
}
|
2025-08-07 13:52:29 +02:00
|
|
|
|
|
|
|
|
|
|
// Helper method for value conversion
|
|
|
|
|
|
_convertValueToUnit(value, fromUnit, toUnit) {
|
2026-03-11 11:13:05 +01:00
|
|
|
|
if ((value === null || typeof value === 'undefined') || !fromUnit || !toUnit || fromUnit === toUnit) {
|
2025-08-07 13:52:29 +02:00
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
return convertModule(value).from(fromUnit).to(toUnit);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.warn(`Conversion failed from ${fromUnit} to ${toUnit}: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Get available units for a measurement type
|
|
|
|
|
|
getAvailableUnits(measurementType = null) {
|
|
|
|
|
|
const type = measurementType || this._currentType;
|
|
|
|
|
|
if (!type) return [];
|
|
|
|
|
|
|
2026-03-11 11:13:05 +01:00
|
|
|
|
const convertMeasure = this.measureMap[this._normalizeType(type)];
|
2025-08-07 13:52:29 +02:00
|
|
|
|
if (!convertMeasure) return [];
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
return convertModule().possibilities(convertMeasure);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Get best unit for current value
|
|
|
|
|
|
getBestUnit(excludeUnits = []) {
|
|
|
|
|
|
const measurement = this.get();
|
|
|
|
|
|
if (!measurement || !measurement.unit) return null;
|
|
|
|
|
|
|
|
|
|
|
|
const currentValue = measurement.getCurrentValue();
|
|
|
|
|
|
if (currentValue === null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const best = convertModule(currentValue)
|
|
|
|
|
|
.from(measurement.unit)
|
|
|
|
|
|
.toBest({ exclude: excludeUnits });
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
2025-08-07 13:52:29 +02:00
|
|
|
|
return best;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.error(`getBestUnit failed: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-23 14:17:42 +02:00
|
|
|
|
_convertPositionStr2Num(positionString) {
|
2025-10-05 09:34:00 +02:00
|
|
|
|
switch(positionString) {
|
2025-09-23 14:17:42 +02:00
|
|
|
|
case "atEquipment":
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
case "upstream":
|
|
|
|
|
|
return Number.POSITIVE_INFINITY;
|
|
|
|
|
|
case "downstream":
|
|
|
|
|
|
return Number.NEGATIVE_INFINITY;
|
2025-10-05 09:34:00 +02:00
|
|
|
|
|
2025-09-23 14:17:42 +02:00
|
|
|
|
default:
|
2025-09-26 13:51:59 +02:00
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.error(`Invalid positionVsParent provided: ${positionString}`);
|
|
|
|
|
|
}
|
2025-09-23 14:17:42 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_convertPositionNum2Str(positionValue) {
|
2026-02-12 10:14:56 +01:00
|
|
|
|
if (positionValue === 0) {
|
2025-09-23 14:17:42 +02:00
|
|
|
|
return "atEquipment";
|
2026-02-12 10:14:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
if (positionValue < 0) {
|
2025-09-23 14:17:42 +02:00
|
|
|
|
return "upstream";
|
2026-02-12 10:14:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
if (positionValue > 0) {
|
2025-09-23 14:17:42 +02:00
|
|
|
|
return "downstream";
|
2025-09-26 13:51:59 +02:00
|
|
|
|
}
|
2026-02-23 13:17:47 +01:00
|
|
|
|
if (this.logger) {
|
|
|
|
|
|
this.logger.warn(`Invalid position provided: ${positionValue}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
2025-09-23 14:17:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-10 12:36:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = MeasurementContainer;
|