From de0b947c56c56f1e06662a3aadab5f2b34c52d87 Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Sun, 5 Oct 2025 09:34:00 +0200 Subject: [PATCH] Updated distance in measurement helper so its a settable compoment. See example.js file in measurement helper folder --- src/helper/childRegistrationUtils.js | 10 +- src/measurements/Measurement.js | 17 +- src/measurements/MeasurementBuilder.js | 10 +- src/measurements/MeasurementContainer.js | 215 +++++++------ src/measurements/examples.js | 370 ++++++++++++++--------- 5 files changed, 348 insertions(+), 274 deletions(-) diff --git a/src/helper/childRegistrationUtils.js b/src/helper/childRegistrationUtils.js index 8710cd0..0aa3034 100644 --- a/src/helper/childRegistrationUtils.js +++ b/src/helper/childRegistrationUtils.js @@ -5,7 +5,7 @@ class ChildRegistrationUtils { this.registeredChildren = new Map(); } - async registerChild(child, positionVsParent) { + async registerChild(child, positionVsParent, distance) { const { softwareType } = child.config.functionality; const { name, id } = child.config.general; @@ -13,7 +13,7 @@ class ChildRegistrationUtils { // Enhanced child setup child.parent = this.mainClass; - //child.positionVsParent = positionVsParent; + child.positionVsParent = positionVsParent; // Enhanced measurement container with rich context if (child.measurements) { @@ -33,10 +33,10 @@ class ChildRegistrationUtils { registeredAt: Date.now() }); - this.logger.debug(`Child ${name} stored under type ${softwareType}`); - this.logger.debug(`Spitting out mainclass: ${this.mainClass.child} `); - + // IMPORTANT: Only call parent registration - no automatic handling and if parent has this function then try to register this child + if (typeof this.mainClass.registerChild === 'function') { this.mainClass.registerChild(child, softwareType); + } this.logger.info(`✅ Child ${name} registered successfully`); } diff --git a/src/measurements/Measurement.js b/src/measurements/Measurement.js index de16718..f175546 100644 --- a/src/measurements/Measurement.js +++ b/src/measurements/Measurement.js @@ -2,11 +2,12 @@ const convertModule = require('../convert/index'); class Measurement { - constructor(type, variant, position, windowSize) { + constructor(type, variant, position, windowSize, distance = null) { this.type = type; // e.g. 'pressure', 'flow', etc. this.variant = variant; // e.g. 'predicted' or 'measured', etc.. this.position = position; // Downstream or upstream of parent object this.windowSize = windowSize; // Rolling window size + this.distance = distance; // Distance from parent, if applicable // Place all data inside an array this.values = []; // Array to store all values @@ -36,13 +37,12 @@ class Measurement { return this; } + setDistance(distance) { + this.distance = distance; + return this; + } + setValue(value, timestamp = Date.now()) { - /* - if (value === undefined || value === null) { - value = null ; - //throw new Error('Value cannot be null or undefined'); - } - */ //shift the oldest value if(this.values.length >= this.windowSize){ @@ -168,7 +168,8 @@ class Measurement { this.type, this.variant, this.position, - this.windowSize + this.windowSize, + this.distance ); // Copy values and timestamps diff --git a/src/measurements/MeasurementBuilder.js b/src/measurements/MeasurementBuilder.js index bead9e3..76ac747 100644 --- a/src/measurements/MeasurementBuilder.js +++ b/src/measurements/MeasurementBuilder.js @@ -5,6 +5,7 @@ class MeasurementBuilder { this.type = null; this.variant = null; this.position = null; + this.distance = null; this.windowSize = 10; // Default window size } @@ -32,6 +33,11 @@ class MeasurementBuilder { return this; } + setDistance(distance) { + this.distance = distance; + return this; + } + build() { // Validate required fields if (!this.type) { @@ -43,12 +49,14 @@ class MeasurementBuilder { if (!this.position) { throw new Error('Measurement position is required'); } + // distance is not a requirement as it can be derived from position return new Measurement( this.type, this.variant, this.position, - this.windowSize + this.windowSize, + this.distance ); diff --git a/src/measurements/MeasurementContainer.js b/src/measurements/MeasurementContainer.js index 0f286cb..a81d49c 100644 --- a/src/measurements/MeasurementContainer.js +++ b/src/measurements/MeasurementContainer.js @@ -3,15 +3,16 @@ const EventEmitter = require('events'); const convertModule = require('../convert/index'); class MeasurementContainer { - constructor(options = {}) { + constructor(options = {},logger) { 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._currentDistance = null; this._unit = null; // Default units for each measurement type @@ -24,11 +25,11 @@ class MeasurementContainer { 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; @@ -39,23 +40,23 @@ class MeasurementContainer { this.childId = null; this.childName = null; this.parentRef = null; - + } // NEW: Methods to set child context setChildId(childId) { - this.childId = childId; - return this; + this.childId = childId; + return this; } setChildName(childName) { - this.childName = childName; - return this; + this.childName = childName; + return this; } setParentRef(parent) { - this.parentRef = parent; - return this; + this.parentRef = parent; + return this; } // New method to set preferred units @@ -66,9 +67,9 @@ class MeasurementContainer { // Get the target unit for a measurement type _getTargetUnit(measurementType) { - return this.preferredUnits[measurementType] || - this.defaultUnits[measurementType] || - null; + return this.preferredUnits[measurementType] || + this.defaultUnits[measurementType] || + null; } // Chainable methods @@ -93,78 +94,84 @@ class MeasurementContainer { throw new Error('Variant must be specified before position'); } - // Turn string positions into numeric values - if (typeof positionValue == "string") { - positionValue = this._convertPositionStr2Num(positionValue); + this._currentPosition = positionValue; + + return this; + } + + distance(distance) { + // If distance is not provided, derive from positionVsParent + if(distance === null) { + distance = this._convertPositionStr2Num(this._currentPosition); } - this._currentPosition = positionValue; + this._currentDistance = distance; return this; } // ENHANCED: Update your existing value method value(val, timestamp = Date.now(), sourceUnit = null) { - if (!this._ensureChainIsValid()) return this; + if (!this._ensureChainIsValid()) return this; + + const measurement = this._getOrCreateMeasurement(); + const targetUnit = this._getTargetUnit(this._currentType); + + let convertedValue = val; + let finalUnit = sourceUnit || targetUnit; - 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}`); + // 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; } - } 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); + measurement.setValue(convertedValue, timestamp); + + if (finalUnit && !measurement.unit) { + measurement.setUnit(finalUnit); + } - 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, + distance: this._currentDistance, + variant: this._currentVariant, + type: this._currentType, + // NEW: Enhanced context + childId: this.childId, + childName: this.childName, + parentRef: this.parentRef, + }; - // 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); + console.log(`Emitted event: ${this._currentType}.${this._currentVariant}.${this._currentPosition}`, eventData); - // Emit the exact event your parent expects - this.emitter.emit(`${this._currentType}.${this._currentVariant}.${this._convertPositionNum2Str(this._currentPosition)}`, eventData); - this.emitter.emit(`${this._currentType}.${this._currentVariant}.${this._currentPosition}`, eventData); - console.log(`Emitted event: ${this._currentType}.${this._currentVariant}.${this._currentPosition}`, eventData); - - return this; + return this; } unit(unitName) { if (!this._ensureChainIsValid()) return this; - + const measurement = this._getOrCreateMeasurement(); measurement.setUnit(unitName); this._unit = unitName; @@ -180,7 +187,7 @@ class MeasurementContainer { getCurrentValue(requestedUnit = null) { const measurement = this.get(); if (!measurement) return null; - + const value = measurement.getCurrentValue(); if (value === null) return null; @@ -207,7 +214,7 @@ class MeasurementContainer { getAverage(requestedUnit = null) { const measurement = this.get(); if (!measurement) return null; - + const avgValue = measurement.getAverage(); if (avgValue === null) return null; @@ -244,34 +251,24 @@ class MeasurementContainer { // 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 - const positions = this.getPositions(); - - this._currentPosition = Math.min(...positions); - const upstream = this.get(); - - this._currentPosition = Math.max(...positions); - const downstream = this.get(); - - this._currentPosition = savedPosition; + } + const upstream = this.measurements?.[this._currentType]?.[this._currentVariant]?.['upstream'] || null; + const downstream = this.measurements?.[this._currentType]?.[this._currentVariant]?.['downstream'] || null; 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); @@ -298,21 +295,22 @@ class MeasurementContainer { 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] = + this.measurements[this._currentType][this._currentVariant][this._currentPosition] = new MeasurementBuilder() .setType(this._currentType) .setVariant(this._currentVariant) .setPosition(this._currentPosition) .setWindowSize(this.windowSize) + .setDistance(this._currentDistance) .build(); } - + return this.measurements[this._currentType][this._currentVariant][this._currentPosition]; } @@ -325,25 +323,21 @@ class MeasurementContainer { if (!this._currentType) { throw new Error('Type must be specified before listing variants'); } - return this.measurements[this._currentType] ? + return this.measurements[this._currentType] ? Object.keys(this.measurements[this._currentType]) : []; } - getPositions(asNumber = false) { + 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]) { + + if (!this.measurements[this._currentType] || + !this.measurements[this._currentType][this._currentVariant]) { return []; } - if (asNumber) { - return Object.keys(this.measurements[this._currentType][this._currentVariant]); - } - - return Object.keys(this.measurements[this._currentType][this._currentVariant]).map(this._convertPositionNum2Str); + return Object.keys(this.measurements[this._currentType][this._currentVariant]); } clear() { @@ -408,7 +402,7 @@ class MeasurementContainer { const best = convertModule(currentValue) .from(measurement.unit) .toBest({ exclude: excludeUnits }); - + return best; } catch (error) { if (this.logger) { @@ -419,16 +413,14 @@ class MeasurementContainer { } _convertPositionStr2Num(positionString) { - - switch (positionString) { - + switch(positionString) { case "atEquipment": return 0; case "upstream": return Number.POSITIVE_INFINITY; case "downstream": return Number.NEGATIVE_INFINITY; - + default: if (this.logger) { this.logger.error(`Invalid positionVsParent provided: ${positionString}`); @@ -438,18 +430,15 @@ class MeasurementContainer { } _convertPositionNum2Str(positionValue) { - if (positionValue === 0) { + switch (positionValue) { + case 0: return "atEquipment"; - } - if (positionValue < 0) { + case (positionValue < 0): return "upstream"; - } - if (positionValue > 0) { + case (positionValue > 0): return "downstream"; - } - - if (this.logger) { - this.logger.error(`Invalid position provided: ${positionValue}`); + default: + console.log(`Invalid position provided: ${positionValue}`); } } diff --git a/src/measurements/examples.js b/src/measurements/examples.js index 7279dee..bc756f5 100644 --- a/src/measurements/examples.js +++ b/src/measurements/examples.js @@ -7,249 +7,325 @@ console.log('retrieving, and converting measurement data with automatic unit han // ==================================== // BASIC SETUP EXAMPLES // ==================================== -console.log('--- Example 1: Basic Setup & Event Subscription ---'); +console.log('--- Example 1: Basic Setup & Distance ---'); // Create a basic container const basicContainer = new MeasurementContainer({ windowSize: 20 }); -// Subscribe to flow events to monitor changes +// Subscribe to events to monitor changes basicContainer.emitter.on('flow.predicted.upstream', (data) => { - console.log(`📡 Event: Flow predicted upstream update: ${data.value} at ${new Date(data.timestamp).toLocaleTimeString()}`); + console.log(`📡 Event: Flow predicted upstream = ${data.value} ${data.unit || ''} (distance=${data.distance ?? 'n/a'}m)`); }); -//show all flow values from variant measured +// Subscribe to all measured flow events using wildcard basicContainer.emitter.on('flow.measured.*', (data) => { - console.log(`📡 Event---------- I DID IT: Flow measured ${data.position} update: ${data.value}`) + console.log(`📡 Event: Flow measured ${data.position} = ${data.value} ${data.unit || ''} (distance=${data.distance ?? 'n/a'}m)`); }); -// Basic value setting with chaining -console.log('Setting basic pressure values...'); -basicContainer.type('pressure').variant('measured').position('upstream').value(100).unit('psi'); -basicContainer.type('pressure').variant('measured').position('downstream').value(95).unit('psi'); -basicContainer.type('pressure').variant('measured').position('downstream').value(80); // Additional value +// Basic value setting with distance +console.log('\nSetting pressure values with distances:'); +basicContainer + .type('pressure') + .variant('measured') + .position('upstream') + .distance(1.5) + .value(100) + .unit('psi'); + +basicContainer + .type('pressure') + .variant('measured') + .position('downstream') + .distance(5.2) + .value(95) + .unit('psi'); + +// Distance persists - no need to set it again for same position +basicContainer + .type('pressure') + .variant('measured') + .position('downstream') + .value(90); // distance 5.2 is automatically reused console.log('✅ Basic setup complete\n'); +// Retrieve and display the distance +const upstreamPressure = basicContainer + .type('pressure') + .variant('measured') + .position('upstream') + .get(); + +console.log(`Retrieved upstream pressure: ${upstreamPressure.getCurrentValue()} ${upstreamPressure.unit}`); +console.log(`Distance from parent: ${upstreamPressure.distance ?? 'not set'} m\n`); + // ==================================== -// AUTO-CONVERSION SETUP EXAMPLES +// AUTO-CONVERSION SETUP // ==================================== console.log('--- Example 2: Auto-Conversion Setup ---'); -console.log('Setting up a container with automatic unit conversion...\n'); -// Create container with auto-conversion enabled const autoContainer = new MeasurementContainer({ autoConvert: true, windowSize: 50, defaultUnits: { - pressure: 'bar', // Default pressure unit - flow: 'l/min', // Default flow unit - power: 'kW', // Default power unit - temperature: 'C' // Default temperature unit + pressure: 'bar', + flow: 'l/min', + power: 'kW', + temperature: 'C' }, preferredUnits: { - pressure: 'psi' // Override: store pressure in PSI instead of bar + pressure: 'psi' } }); -// Values are automatically converted to preferred units -console.log('Adding pressure data with auto-conversion:'); -autoContainer.type('pressure').variant('measured').position('upstream') - .value(1.5, Date.now(), 'bar'); // Input: 1.5 bar → Auto-stored as ~21.76 psi +// Values automatically convert to preferred units +console.log('Adding pressure with auto-conversion:'); +autoContainer + .type('pressure') + .variant('measured') + .position('upstream') + .distance(0.5) + .value(1.5, Date.now(), 'bar'); // Input: 1.5 bar → Auto-stored as ~21.76 psi -autoContainer.type('pressure').variant('measured').position('downstream') - .value(20, Date.now(), 'psi'); // Input: 20 psi → Stored as 20 psi (already in preferred unit) +const converted = autoContainer + .type('pressure') + .variant('measured') + .position('upstream') + .get(); -// Check what was actually stored -const storedPressure = autoContainer.type('pressure').variant('measured').position('upstream').get(); -console.log(` Stored upstream pressure: ${storedPressure.getCurrentValue()} ${storedPressure.unit}`); -console.log(' Auto-conversion setup complete\n'); +console.log(`Stored as: ${converted.getCurrentValue()} ${converted.unit} (distance=${converted.distance}m)`); +console.log('✅ Auto-conversion complete\n'); // ==================================== -// UNIT CONVERSION EXAMPLES +// UNIT CONVERSION ON RETRIEVAL // ==================================== console.log('--- Example 3: Unit Conversion on Retrieval ---'); -console.log('Getting values in different units without changing stored data...\n'); -// Add flow data in different units -autoContainer.type('flow').variant('predicted').position('upstream') - .value(100, Date.now(), 'l/min'); // Stored in l/min (default) +autoContainer + .type('flow') + .variant('predicted') + .position('upstream') + .distance(2.4) + .value(100, Date.now(), 'l/min'); -autoContainer.type('flow').variant('predicted').position('downstream') - .value(6, Date.now(), 'm3/h'); // Auto-converted from m3/h to l/min +const flowMeasurement = autoContainer + .type('flow') + .variant('predicted') + .position('upstream') + .get(); -// Retrieve the same data in different units -const flowLPM = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('l/min'); -const flowM3H = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('m3/h'); -const flowGPM = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('gal/min'); - -console.log(`Flow in l/min: ${flowLPM}`); -console.log(`Flow in m³/h: ${flowM3H.toFixed(2)}`); -console.log(`Flow in gal/min: ${flowGPM.toFixed(2)}`); -console.log('Unit conversion examples complete\n'); +console.log(`Flow in l/min: ${flowMeasurement.getCurrentValue('l/min')}`); +console.log(`Flow in m³/h: ${flowMeasurement.getCurrentValue('m3/h').toFixed(2)}`); +console.log(`Flow in gal/min: ${flowMeasurement.getCurrentValue('gal/min').toFixed(2)}`); +console.log(`Distance: ${flowMeasurement.distance}m\n`); // ==================================== // SMART UNIT SELECTION // ==================================== console.log('--- Example 4: Smart Unit Selection ---'); -console.log('Automatically finding the best unit for readability...\n'); -// Add a very small pressure value -autoContainer.type('pressure').variant('test').position('sensor') +autoContainer + .type('pressure') + .variant('test') + .position('sensor') + .distance(0.2) .value(0.001, Date.now(), 'bar'); -// Get the best unit for this small value -const bestUnit = autoContainer.type('pressure').variant('test').position('sensor').getBestUnit(); +const bestUnit = autoContainer + .type('pressure') + .variant('test') + .position('sensor') + .getBestUnit(); + if (bestUnit) { - console.log(`Best unit representation: ${bestUnit.val} ${bestUnit.unit}`); + console.log(`Best unit: ${bestUnit.val.toFixed(2)} ${bestUnit.unit}`); } -// Get all available units for pressure const availableUnits = autoContainer.getAvailableUnits('pressure'); -console.log(`Available pressure units: ${availableUnits.slice(0, 8).join(', ')}... (${availableUnits.length} total)`); -console.log('Smart unit selection complete\n'); +console.log(`Available units: ${availableUnits.slice(0, 5).join(', ')}...\n`); // ==================================== -// BASIC RETRIEVAL AND CALCULATIONS +// BASIC RETRIEVAL // ==================================== console.log('--- Example 5: Basic Value Retrieval ---'); -console.log('Getting individual values and their units...\n'); -// Using basic container for clear examples -const upstreamValue = basicContainer.type('pressure').variant('measured').position('upstream').getCurrentValue(); -const upstreamUnit = basicContainer.type('pressure').variant('measured').position('upstream').get().unit; -console.log(`Upstream pressure: ${upstreamValue} ${upstreamUnit}`); +const upstreamVal = basicContainer + .type('pressure') + .variant('measured') + .position('upstream') + .getCurrentValue(); -const downstreamValue = basicContainer.type('pressure').variant('measured').position('downstream').getCurrentValue(); -const downstreamUnit = basicContainer.type('pressure').variant('measured').position('downstream').get().unit; -console.log(`Downstream pressure: ${downstreamValue} ${downstreamUnit}`); -console.log('Basic retrieval complete\n'); +const upstreamData = basicContainer + .type('pressure') + .variant('measured') + .position('upstream') + .get(); + +console.log(`Upstream: ${upstreamVal} ${upstreamData.unit} at ${upstreamData.distance}m`); + +const downstreamVal = basicContainer + .type('pressure') + .variant('measured') + .position('downstream') + .getCurrentValue(); + +const downstreamData = basicContainer + .type('pressure') + .variant('measured') + .position('downstream') + .get(); + +console.log(`Downstream: ${downstreamVal} ${downstreamData.unit} at ${downstreamData.distance}m\n`); // ==================================== -// CALCULATIONS AND STATISTICS +// CALCULATIONS & STATISTICS // ==================================== console.log('--- Example 6: Calculations & Statistics ---'); -console.log('Using built-in calculation methods...\n'); -// Add flow data for calculations -basicContainer.type('flow').variant('predicted').position('upstream').value(200).unit('gpm'); -basicContainer.type('flow').variant('predicted').position('downstream').value(195).unit('gpm'); +basicContainer + .type('flow') + .variant('predicted') + .position('upstream') + .distance(3.0) + .value(200) + .unit('gpm'); -const flowAvg = basicContainer.type('flow').variant('predicted').position('upstream').getAverage(); -console.log(`Average upstream flow: ${flowAvg} gpm`); +basicContainer + .type('flow') + .variant('predicted') + .position('downstream') + .distance(8.5) + .value(195) + .unit('gpm'); -// Calculate pressure difference between upstream and downstream -const pressureDiff = basicContainer.type('pressure').variant('measured').difference(); -console.log(`Pressure difference: ${pressureDiff.value} ${pressureDiff.unit}`); -console.log('Calculations complete\n'); +const flowAvg = basicContainer + .type('flow') + .variant('predicted') + .position('upstream') + .getAverage(); + +console.log(`Average upstream flow: ${flowAvg.toFixed(1)} gpm`); + +const pressureDiff = basicContainer + .type('pressure') + .variant('measured') + .difference(); + +console.log(`Pressure difference: ${pressureDiff.value} ${pressureDiff.unit}\n`); // ==================================== -// ADVANCED STATISTICS +// ADVANCED STATISTICS & HISTORY // ==================================== console.log('--- Example 7: Advanced Statistics & History ---'); -console.log('Adding multiple values and getting comprehensive statistics...\n'); -// Add several flow measurements to build history -basicContainer.type('flow').variant('measured').position('upstream') - .value(210).value(215).value(205).value(220).value(200).unit('m3/h'); -basicContainer.type('flow').variant('measured').position('downstream') - .value(190).value(195).value(185).value(200).value(180).unit('m3/h'); +basicContainer + .type('flow') + .variant('measured') + .position('upstream') + .distance(3.0) + .value(210) + .value(215) + .value(205) + .value(220) + .value(200) + .unit('m3/h'); + +const stats = basicContainer + .type('flow') + .variant('measured') + .position('upstream'); + +const statsData = stats.get(); -// Get comprehensive statistics -const measurement = basicContainer.type('flow').variant('measured').position('upstream'); console.log('Flow Statistics:'); -console.log(`- Current value: ${measurement.getCurrentValue()} ${measurement.get().unit}`); -console.log(`- Average: ${measurement.getAverage().toFixed(1)} ${measurement.get().unit}`); -console.log(`- Minimum: ${measurement.getMin()} ${measurement.get().unit}`); -console.log(`- Maximum: ${measurement.getMax()} ${measurement.get().unit}`); +console.log(` Current: ${stats.getCurrentValue()} ${statsData.unit}`); +console.log(` Average: ${stats.getAverage().toFixed(1)} ${statsData.unit}`); +console.log(` Min: ${stats.getMin()} ${statsData.unit}`); +console.log(` Max: ${stats.getMax()} ${statsData.unit}`); +console.log(` Distance: ${statsData.distance}m`); -// Show all values with timestamps -const allValues = measurement.getAllValues(); -console.log(`- Total samples: ${allValues.values.length}`); -console.log(`- Value history: [${allValues.values.join(', ')}]`); -console.log('Advanced statistics complete\n'); +const allValues = stats.getAllValues(); +console.log(` Samples: ${allValues.values.length}`); +console.log(` History: [${allValues.values.join(', ')}]\n`); // ==================================== // DYNAMIC UNIT MANAGEMENT // ==================================== console.log('--- Example 8: Dynamic Unit Management ---'); -console.log('Changing preferred units at runtime...\n'); -// Change preferred unit for flow measurements autoContainer.setPreferredUnit('flow', 'm3/h'); console.log('Changed preferred flow unit to m³/h'); -// Add new flow data - will auto-convert to new preferred unit -autoContainer.type('flow').variant('realtime').position('inlet') - .value(150, Date.now(), 'l/min'); // Input in l/min, stored as m³/h +autoContainer + .type('flow') + .variant('realtime') + .position('inlet') + .distance(1.2) + .value(150, Date.now(), 'l/min'); -const realtimeFlow = autoContainer.type('flow').variant('realtime').position('inlet'); -console.log(`Stored as: ${realtimeFlow.getCurrentValue()} ${realtimeFlow.get().unit}`); -console.log(`Original unit: ${realtimeFlow.getCurrentValue('l/min')} l/min`); -console.log('Dynamic unit management complete\n'); +const realtimeFlow = autoContainer + .type('flow') + .variant('realtime') + .position('inlet') + .get(); + +console.log(`Stored as: ${realtimeFlow.getCurrentValue()} ${realtimeFlow.unit}`); +console.log(`Original: ${realtimeFlow.getCurrentValue('l/min').toFixed(1)} l/min`); +console.log(`Distance: ${realtimeFlow.distance}m\n`); // ==================================== // DATA EXPLORATION // ==================================== console.log('--- Example 9: Data Exploration ---'); -console.log('Discovering what data is available in the container...\n'); -console.log('Available measurement types:', basicContainer.getTypes()); +console.log('Available types:', basicContainer.getTypes()); console.log('Pressure variants:', basicContainer.type('pressure').getVariants()); console.log('Measured pressure positions:', basicContainer.type('pressure').variant('measured').getPositions()); -// Show data structure overview -console.log('\nData Structure Overview:'); +console.log('\nData Structure:'); basicContainer.getTypes().forEach(type => { - console.log(`${type.toUpperCase()}:`); const variants = basicContainer.type(type).getVariants(); - variants.forEach(variant => { - const positions = basicContainer.type(type).variant(variant).getPositions(); - positions.forEach(position => { - const measurement = basicContainer.type(type).variant(variant).position(position).get(); - if (measurement && measurement.values.length > 0) { - console.log(` └── ${variant}.${position}: ${measurement.values.length} values (${measurement.unit || 'no unit'})`); - } + if (variants.length > 0) { + console.log(`${type.toUpperCase()}:`); + variants.forEach(variant => { + const positions = basicContainer.type(type).variant(variant).getPositions(); + positions.forEach(position => { + const m = basicContainer.type(type).variant(variant).position(position).get(); + if (m && m.values.length > 0) { + console.log(` └─ ${variant}.${position}: ${m.values.length} values, ${m.unit || 'no unit'}, dist=${m.distance ?? 'n/a'}m`); + } + }); }); - }); + } }); -console.log('Data exploration complete\n'); + +console.log('\n✅ All examples complete!\n'); // ==================================== -// BEST PRACTICES SUMMARY +// BEST PRACTICES // ==================================== -console.log('--- Best Practices Summary ---'); -console.log('BEST PRACTICES FOR NEW USERS:\n'); +console.log('--- Best Practices Summary ---\n'); -console.log('1. SETUP:'); -console.log(' • Enable auto-conversion for consistent units'); -console.log(' • Define default units for your measurement types'); -console.log(' • Set appropriate window size for your data needs\n'); +console.log('SETUP:'); +console.log(' • Enable autoConvert for consistent units'); +console.log(' • Define defaultUnits for your measurement types'); +console.log(' • Set windowSize based on your data retention needs\n'); -console.log('2. STORING DATA:'); -console.log(' • Always use the full chain: type().variant().position().value()'); -console.log(' • Specify source unit when adding values: .value(100, timestamp, "psi")'); -console.log(' • Set units immediately after first value: .value(100).unit("psi")\n'); +console.log('STORING DATA:'); +console.log(' • Chain methods: type().variant().position().distance().value()'); +console.log(' • Set distance once - it persists for that position'); +console.log(' • Specify source unit: .value(100, timestamp, "psi")'); +console.log(' • Set unit immediately: .value(100).unit("psi")\n'); -console.log('3. RETRIEVING DATA:'); -console.log(' • Use .getCurrentValue("unit") to get values in specific units'); -console.log(' • Use .getBestUnit() for automatic unit selection'); -console.log(' • Use .difference() for automatic upstream/downstream calculations\n'); +console.log('RETRIEVING DATA:'); +console.log(' • Use .getCurrentValue("unit") for specific units'); +console.log(' • Use .getBestUnit() for automatic selection'); +console.log(' • Use .difference() for upstream/downstream deltas'); +console.log(' • Access .get().distance for physical positioning\n'); -console.log('4. MONITORING:'); -console.log(' • Subscribe to events for real-time updates'); -console.log(' • Use .emitter.on("type.variant.position", callback)'); -console.log(' • Explore available data with .getTypes(), .getVariants(), .getPositions()\n'); +console.log('MONITORING:'); +console.log(' • Subscribe: .emitter.on("type.variant.position", callback)'); +console.log(' • Event data includes: value, unit, timestamp, distance'); +console.log(' • Explore data: .getTypes(), .getVariants(), .getPositions()\n'); -console.log('All examples complete! Ready to use MeasurementContainer'); - -// Export for programmatic use -module.exports = { - runExamples: () => { - console.log('Measurement Container Examples - Complete Guide for New Users'); - console.log('This file demonstrates all features with practical examples.'); - }, - - // Export containers for testing - basicContainer, - autoContainer -}; \ No newline at end of file +module.exports = { basicContainer, autoContainer }; \ No newline at end of file