const MeasurementBuilder = require('./MeasurementBuilder'); const EventEmitter = require('events'); const convertModule = require('../convert/index'); class MeasurementContainer { constructor(options = {}) { this.emitter = new EventEmitter(); this.measurements = {}; this.windowSize = options.windowSize || 10; // Default window size // For chaining context this._currentType = null; this._currentVariant = null; this._currentPosition = null; this._unit = null; // Default units for each measurement type this.defaultUnits = { pressure: 'mbar', flow: 'm3/h', power: 'kW', temperature: 'C', volume: 'm3', length: 'm', ...options.defaultUnits // Allow override }; // Auto-conversion settings this.autoConvert = options.autoConvert !== false; // Default to true this.preferredUnits = options.preferredUnits || {}; // Per-measurement overrides // 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; } 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; } // Get the target unit for a measurement type _getTargetUnit(measurementType) { return this.preferredUnits[measurementType] || this.defaultUnits[measurementType] || null; } // Chainable methods type(typeName) { this._currentType = typeName; this._currentVariant = null; this._currentPosition = null; return this; } variant(variantName) { if (!this._currentType) { throw new Error('Type must be specified before variant'); } this._currentVariant = variantName; this._currentPosition = null; return this; } position(positionName) { if (!this._currentVariant) { throw new Error('Variant must be specified before position'); } this._currentPosition = positionName; return this; } // ENHANCED: Update your existing value method value(val, timestamp = Date.now(), sourceUnit = null) { if (!this._ensureChainIsValid()) return this; const measurement = this._getOrCreateMeasurement(); const targetUnit = this._getTargetUnit(this._currentType); let convertedValue = val; let finalUnit = sourceUnit || targetUnit; // Auto-convert if enabled and units are specified if (this.autoConvert && sourceUnit && targetUnit && sourceUnit !== targetUnit) { try { convertedValue = convertModule(val).from(sourceUnit).to(targetUnit); finalUnit = targetUnit; if (this.logger) { this.logger.debug(`Auto-converted ${val} ${sourceUnit} to ${convertedValue} ${targetUnit}`); } } catch (error) { if (this.logger) { this.logger.warn(`Auto-conversion failed from ${sourceUnit} to ${targetUnit}: ${error.message}`); } convertedValue = val; finalUnit = sourceUnit; } } measurement.setValue(convertedValue, timestamp); if (finalUnit && !measurement.unit) { measurement.setUnit(finalUnit); } // ENHANCED: Emit event with rich context const eventData = { value: convertedValue, originalValue: val, unit: finalUnit, sourceUnit: sourceUnit, timestamp, position: this._currentPosition, 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); return this; } 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; return this._getOrCreateMeasurement(); } getCurrentValue(requestedUnit = null) { const measurement = this.get(); if (!measurement) return null; const value = measurement.getCurrentValue(); if (value === null) return null; // Return as-is if no unit conversion requested if (!requestedUnit) { return value; } // Convert if needed if (measurement.unit && requestedUnit !== measurement.unit) { try { return convertModule(value).from(measurement.unit).to(requestedUnit); } catch (error) { if (this.logger) { this.logger.error(`Unit conversion failed: ${error.message}`); } return value; // Return original value if conversion fails } } 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; } // Difference calculations between positions difference(requestedUnit = null) { if (!this._currentType || !this._currentVariant) { throw new Error('Type and variant must be specified for difference calculation'); } const savedPosition = this._currentPosition; // Get upstream and downstream measurements this._currentPosition = 'upstream'; const upstream = this.get(); this._currentPosition = 'downstream'; const downstream = this.get(); this._currentPosition = savedPosition; if (!upstream || !downstream || upstream.values.length === 0 || downstream.values.length === 0) { return null; } // Get target unit for conversion const targetUnit = requestedUnit || upstream.unit || downstream.unit; // Get values in the same unit const upstreamValue = this._convertValueToUnit(upstream.getCurrentValue(), upstream.unit, targetUnit); const downstreamValue = this._convertValueToUnit(downstream.getCurrentValue(), downstream.unit, targetUnit); const upstreamAvg = this._convertValueToUnit(upstream.getAverage(), upstream.unit, targetUnit); const downstreamAvg = this._convertValueToUnit(downstream.getAverage(), downstream.unit, targetUnit); return { value: downstreamValue - upstreamValue, avgDiff: downstreamAvg - upstreamAvg, unit: targetUnit }; } // 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] = {}; } if (!this.measurements[this._currentType][this._currentVariant][this._currentPosition]) { this.measurements[this._currentType][this._currentVariant][this._currentPosition] = new MeasurementBuilder() .setType(this._currentType) .setVariant(this._currentVariant) .setPosition(this._currentPosition) .setWindowSize(this.windowSize) .build(); } return this.measurements[this._currentType][this._currentVariant][this._currentPosition]; } // Additional utility methods getTypes() { return Object.keys(this.measurements); } getVariants() { if (!this._currentType) { throw new Error('Type must be specified before listing variants'); } return this.measurements[this._currentType] ? Object.keys(this.measurements[this._currentType]) : []; } getPositions() { if (!this._currentType || !this._currentVariant) { throw new Error('Type and variant must be specified before listing positions'); } 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; } // Helper method for value conversion _convertValueToUnit(value, fromUnit, toUnit) { if (!value || !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 []; // Map measurement types to convert module measures const measureMap = { pressure: 'pressure', flow: 'volumeFlowRate', power: 'power', temperature: 'temperature', volume: 'volume', length: 'length', mass: 'mass', energy: 'energy' }; const convertMeasure = measureMap[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; } } } module.exports = MeasurementContainer;