Files
generalFunctions/src/measurements/MeasurementContainer.js
Rene De Ren a516c2b2b6 MeasurementContainer.get: strict-resolve explicit .child(name)
A read chain `.child(name).getCurrentValue()` previously fell through
silently to the implicit-default child or the first available sibling
when the named child did not exist. Caller asked for X, got Y, no
warning. Surfaced via pumpingStation spillPrev: a fresh basin's
.child('overflow').getCurrentValue() returned the value of
'manual-qout' (the only existing child at that position).

Split the resolution into two strictness levels:
  _currentChildId (per-chain .child(name)) → STRICT, missing = null.
  this.childId    (persistent setChildId)  → HINT, falls back to
                                              'default' then first.

The persistent path is what registered children (rotatingMachine etc.)
rely on: they write under composed ids ('up-<id>') but expect reads
without explicit .child() to still resolve.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:17:58 +02:00

873 lines
28 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const MeasurementBuilder = require('./MeasurementBuilder');
const EventEmitter = require('events');
const convertModule = require('../convert/index');
const { POSITIONS } = require('../constants/positions');
/* ============================================================================
* MeasurementContainer — measurement storage with chainable type/variant/
* position/child addressing.
*
* INTERNAL STORAGE SHAPE
* measurements[type][variant][position][childId] = Measurement instance
*
* The childId layer is ALWAYS present, even when the caller doesn't specify
* one. _getOrCreateMeasurement defaults childId to 'default' when no
* .child(...) is in the chain. So writing
*
* mc.type('level').variant('measured').position('atequipment')
* .value(2.5, ts, 'm');
*
* stores the value at measurements.level.measured.atequipment.default.
*
* READING — the chainable getters resolve the default child transparently,
* so consumers usually don't see it:
*
* mc.type('level').variant('measured').position('atequipment')
* .getCurrentValue('m'); // returns 2.5
*
* FLATTENED OUTPUT — getFlattenedOutput() emits ONE key per child, including
* the implicit 'default' bucket:
*
* {
* 'level.measured.atequipment.default': 2.5, // implicit child
* 'flow.predicted.in.manual-qin': 0.05, // explicit .child('manual-qin')
* 'flow.predicted.in.from-pump-A': 0.03,
* …
* }
*
* ⚠ DASHBOARDS / DOWNSTREAM PARSERS MUST INCLUDE THE CHILD KEY
* The flat key format is `${type}.${variant}.${position}.${childId}`.
* When you have not used .child(), the childId is the literal string
* 'default'. Use 'level.measured.atequipment.default', NOT
* 'level.measured.atequipment'. This trips up new consumers — see the
* pumpingStation basic-dashboard parser for an example that gets it right.
*
* AGGREGATION — sum() folds all children of a position into one number:
*
* mc.sum('flow', 'predicted', ['in'], 'm3/s');
* // = manual-qin + from-pump-A + … + (default if any)
* ============================================================================
*/
class MeasurementContainer {
constructor(options = {},logger) {
this.logger = logger || null;
this.emitter = new EventEmitter();
this.measurements = {};
this.windowSize = options.windowSize || 10; // Default window size
// For chaining context
this._currentChildId = null;
this._currentType = null;
this._currentVariant = null;
this._currentPosition = null;
this._currentDistance = null;
this._unit = null;
// Default units for each measurement type (ingress/preferred)
this.defaultUnits = {
pressure: 'mbar',
flow: 'm3/h',
power: 'kW',
temperature: 'C',
volume: 'm3',
length: 'm',
...options.defaultUnits // Allow override
};
// 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,
};
// Auto-conversion settings
this.autoConvert = options.autoConvert !== false; // Default to true
this.preferredUnits = options.preferredUnits || {}; // Per-measurement overrides
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',
};
// 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;
}
// NEW: Methods to set child context
setChildId(childId) {
this.childId = childId;
return this;
}
child(childId) {
this._currentChildId = childId || 'default';
return this;
}
setChildName(childName) {
this.childName = childName;
return this;
}
setParentRef(parent) {
this.parentRef = parent;
return this;
}
// New method to set preferred units
setPreferredUnit(measurementType, unit) {
this.preferredUnits[measurementType] = unit;
return this;
}
setCanonicalUnit(measurementType, unit) {
this.canonicalUnits[measurementType] = unit;
return this;
}
// Get the target unit for a measurement type
_getTargetUnit(measurementType) {
return this.preferredUnits[measurementType] ||
this.defaultUnits[measurementType] ||
null;
}
_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) {
// Unknown type (not in measureMap): accept any unit. This lets user-
// defined measurement types (e.g. 'humidity', 'co2', arbitrary IoT
// channels in digital mode) pass through without being rejected just
// because their unit string ('%', 'ppm', …) is not a known physical
// unit to the convert module. Known types are still validated strictly.
const normalizedType = this._normalizeType(measurementType);
const expectedMeasure = this.measureMap[normalizedType];
if (!expectedMeasure) return true;
const desc = this._describeUnit(unit);
if (!desc) return false;
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,
};
}
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;
}
// Chainable methods
type(typeName) {
this._currentType = typeName;
this._currentVariant = null;
this._currentPosition = null;
this._currentChildId = null;
return this;
}
variant(variantName) {
if (!this._currentType) {
if (this.logger) {
this.logger.warn('variant() ignored: type must be specified before variant');
}
return this;
}
this._currentVariant = variantName;
this._currentPosition = null;
this._currentChildId = null;
return this;
}
position(positionValue) {
if (!this._currentVariant) {
if (this.logger) {
this.logger.warn('position() ignored: variant must be specified before position');
}
return this;
}
this._currentPosition = positionValue.toString().toLowerCase();
return this;
}
distance(distance) {
// If distance is not provided, derive from positionVsParent
if(distance === null) {
distance = this._convertPositionStr2Num(this._currentPosition);
}
this._currentDistance = distance;
return this;
}
// ENHANCED: Update your existing value method
value(val, timestamp = Date.now(), sourceUnit = null) {
if (!this._ensureChainIsValid()) return this;
const unitPolicy = this._resolveUnitPolicy(this._currentType, sourceUnit);
if (!unitPolicy.valid) return this;
const measurement = this._getOrCreateMeasurement();
const targetUnit = unitPolicy.storageUnit;
let convertedValue = val;
let finalUnit = targetUnit || unitPolicy.sourceUnit;
// Auto-convert if enabled and units are specified
if (this.autoConvert && unitPolicy.sourceUnit && targetUnit && unitPolicy.sourceUnit !== targetUnit) {
try {
convertedValue = convertModule(val).from(unitPolicy.sourceUnit).to(targetUnit);
finalUnit = targetUnit;
if (this.logger) {
this.logger.debug(`Auto-converted ${val} ${unitPolicy.sourceUnit} to ${convertedValue} ${targetUnit}`);
}
} catch (error) {
const message = `Auto-conversion failed from ${unitPolicy.sourceUnit} to ${targetUnit}: ${error.message}`;
if (this.strictUnitValidation) {
this._handleUnitViolation(message);
return this;
}
if (this.logger) this.logger.warn(message);
convertedValue = val;
finalUnit = unitPolicy.sourceUnit;
}
}
measurement.setValue(convertedValue, timestamp);
if (finalUnit) {
measurement.setUnit(finalUnit);
}
// ENHANCED: Emit event with rich context
const eventData = {
value: convertedValue,
originalValue: val,
unit: finalUnit,
sourceUnit: unitPolicy.sourceUnit,
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);
//console.log(`Emitted event: ${this._currentType}.${this._currentVariant}.${this._currentPosition}`, eventData);
return this;
}
/**
* 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;
}
unit(unitName) {
if (!this._ensureChainIsValid()) return this;
const measurement = this._getOrCreateMeasurement();
measurement.setUnit(unitName);
this._unit = unitName;
return this;
}
// Terminal operations - get data out
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 lookup. Two separate sources of "child-id" on the
// container, with DIFFERENT strictness:
//
// _currentChildId : transient, set by .child(name) inside a chain.
// Explicit per-call. STRICT — if the named child
// does not exist, return null. Silent fall-through
// to a sibling would mask a missing-stream read
// as a wrong-stream read (see pumpingStation
// spillPrev bug, 2026-05-06).
//
// this.childId : persistent, set by setChildId(id). HINT only —
// try it first, then fall back to 'default' then
// first available. Containers registered with a
// persistent id (rotatingMachine, etc.) write
// under composed child ids (e.g. 'up-<id>') that
// don't equal the persistent id, and reads must
// still resolve to those writes.
if (posBucket && typeof posBucket === 'object') {
const keys = Object.keys(posBucket);
if (!keys.length) return null;
if (this._currentChildId) {
return posBucket[this._currentChildId] || null;
}
return (this.childId && posBucket[this.childId]) ||
posBucket.default ||
posBucket[keys[0]] ||
null;
}
return null;
}
getCurrentValue(requestedUnit = null) {
const measurement = this.get();
if (!measurement) return null;
const value = measurement.getCurrentValue();
if (value === null) return null;
if (!requestedUnit || !measurement.unit || requestedUnit === measurement.unit) {
return value;
}
try {
return convertModule(value).from(measurement.unit).to(requestedUnit);
} catch (error) {
if (this.logger) this.logger.error(`Unit conversion failed: ${error.message}`);
return value;
}
}
getAverage(requestedUnit = null) {
const measurement = this.get();
if (!measurement) return null;
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;
}
}
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;
}
getLaggedValue(lag = 1,requestedUnit = null ){
const measurement = this.get();
if (!measurement) return null;
let sample = measurement.getLaggedSample(lag);
if (sample === null) return null;
const value = sample.value;
// Return as-is if no unit conversion requested
if (!requestedUnit) {
return value;
}
// Convert if needed
if (measurement.unit && requestedUnit !== measurement.unit) {
try {
const convertedValue = convertModule(sample.value).from(measurement.unit).to(requestedUnit);
//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 value;
}
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 {
const convertedValue = convertModule(sample.value).from(measurement.unit).to(requestedUnit);
//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;
}
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);
}
/**
* Flatten the entire container to a key→value map, suitable for
* dashboards / InfluxDB / debug dumps.
*
* KEY FORMAT — child-bucketed series (the common case):
* `${type}.${variant}.${position}.${childId}`
*
* Even measurements written without an explicit `.child(...)` end up
* here under `childId === 'default'` (see _getOrCreateMeasurement).
* Examples:
* level.measured.atequipment.default // implicit child
* flow.predicted.in.manual-qin // explicit child
* flow.predicted.in.from-pump-A // explicit child
*
* Consumers (Node-RED dashboards, parsers) MUST include the trailing
* `.default` when reading default-bucket measurements. Stripping it
* silently misses the value. This is the #1 footgun for new code that
* uses MeasurementContainer.
*
* The "Legacy single series" branch below catches a pre-v2 storage
* shape where a position held a Measurement directly (no child layer);
* new code never produces that shape but old serialized state may.
*/
getFlattenedOutput(options = {}) {
const requestedUnits = options.requestedUnits || (options.usePreferredUnits ? this.preferredUnits : null);
const out = {};
Object.entries(this.measurements).forEach(([type, variants]) => {
Object.entries(variants).forEach(([variant, positions]) => {
Object.entries(positions).forEach(([position, entry]) => {
// Legacy single series (no childId layer)
if (entry?.getCurrentValue) {
out[`${type}.${variant}.${position}`] = this._resolveOutputValue(type, entry, requestedUnits);
return;
}
// Child-bucketed series — ALWAYS the case for new writes,
// including the implicit 'default' bucket when no .child() is
// used. The flat key carries the childId.
if (entry && typeof entry === 'object') {
Object.entries(entry).forEach(([childId, m]) => {
if (m?.getCurrentValue) {
out[`${type}.${variant}.${position}.${childId}`] = this._resolveOutputValue(type, m, requestedUnits);
}
});
}
});
});
});
return out;
}
// Difference calculations between positions
difference({ from = POSITIONS.DOWNSTREAM, to = POSITIONS.UPSTREAM, unit: requestedUnit } = {}) {
if (!this._currentType || !this._currentVariant) {
if (this.logger) {
this.logger.warn('difference() ignored: type and variant must be specified');
}
return null;
}
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;
};
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);
const aAvg = this._convertValueToUnit(a.getAverage(), a.unit, targetUnit);
const bAvg = this._convertValueToUnit(b.getAverage(), b.unit, targetUnit);
return { value: aVal - bVal, avgDiff: aAvg - bAvg, unit: targetUnit, from, to };
}
// 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] = {};
}
if (!this.measurements[this._currentType][this._currentVariant]) {
this.measurements[this._currentType][this._currentVariant] = {};
}
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] = {};
}
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];
}
// Additional utility methods
getTypes() {
return Object.keys(this.measurements);
}
getVariants() {
if (!this._currentType) {
if (this.logger) {
this.logger.warn('getVariants() ignored: type must be specified first');
}
return [];
}
return this.measurements[this._currentType] ?
Object.keys(this.measurements[this._currentType]) : [];
}
_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);
}
getPositions() {
if (!this._currentType || !this._currentVariant) {
if (this.logger) {
this.logger.warn('getPositions() ignored: type and variant must be specified first');
}
return [];
}
if (!this.measurements[this._currentType] ||
!this.measurements[this._currentType][this._currentVariant]) {
return [];
}
return Object.keys(this.measurements[this._currentType][this._currentVariant]);
}
clear() {
this.measurements = {};
this._currentType = null;
this._currentVariant = null;
this._currentPosition = null;
this._currentDistance = null;
this._unit = null;
}
// Helper method for value conversion
_convertValueToUnit(value, fromUnit, toUnit) {
if ((value === null || typeof value === 'undefined') || !fromUnit || !toUnit || fromUnit === toUnit) {
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 [];
const convertMeasure = this.measureMap[this._normalizeType(type)];
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 });
return best;
} catch (error) {
if (this.logger) {
this.logger.error(`getBestUnit failed: ${error.message}`);
}
return null;
}
}
_convertPositionStr2Num(positionString) {
switch(positionString) {
case POSITIONS.AT_EQUIPMENT:
return 0;
case POSITIONS.UPSTREAM:
return Number.POSITIVE_INFINITY;
case POSITIONS.DOWNSTREAM:
return Number.NEGATIVE_INFINITY;
default:
if (this.logger) {
this.logger.error(`Invalid positionVsParent provided: ${positionString}`);
}
return;
}
}
_convertPositionNum2Str(positionValue) {
if (positionValue === 0) {
return POSITIONS.AT_EQUIPMENT;
}
if (positionValue < 0) {
return POSITIONS.UPSTREAM;
}
if (positionValue > 0) {
return POSITIONS.DOWNSTREAM;
}
if (this.logger) {
this.logger.warn(`Invalid position provided: ${positionValue}`);
}
return null;
}
}
module.exports = MeasurementContainer;