Updated distance in measurement helper so its a settable compoment. See example.js file in measurement helper folder

This commit is contained in:
znetsixe
2025-10-05 09:34:00 +02:00
parent d99561fa80
commit de0b947c56
5 changed files with 348 additions and 274 deletions

View File

@@ -5,7 +5,7 @@ class ChildRegistrationUtils {
this.registeredChildren = new Map(); this.registeredChildren = new Map();
} }
async registerChild(child, positionVsParent) { async registerChild(child, positionVsParent, distance) {
const { softwareType } = child.config.functionality; const { softwareType } = child.config.functionality;
const { name, id } = child.config.general; const { name, id } = child.config.general;
@@ -13,7 +13,7 @@ class ChildRegistrationUtils {
// Enhanced child setup // Enhanced child setup
child.parent = this.mainClass; child.parent = this.mainClass;
//child.positionVsParent = positionVsParent; child.positionVsParent = positionVsParent;
// Enhanced measurement container with rich context // Enhanced measurement container with rich context
if (child.measurements) { if (child.measurements) {
@@ -33,10 +33,10 @@ class ChildRegistrationUtils {
registeredAt: Date.now() registeredAt: Date.now()
}); });
this.logger.debug(`Child ${name} stored under type ${softwareType}`); // IMPORTANT: Only call parent registration - no automatic handling and if parent has this function then try to register this child
this.logger.debug(`Spitting out mainclass: ${this.mainClass.child} `); if (typeof this.mainClass.registerChild === 'function') {
this.mainClass.registerChild(child, softwareType); this.mainClass.registerChild(child, softwareType);
}
this.logger.info(`✅ Child ${name} registered successfully`); this.logger.info(`✅ Child ${name} registered successfully`);
} }

View File

@@ -2,11 +2,12 @@
const convertModule = require('../convert/index'); const convertModule = require('../convert/index');
class Measurement { class Measurement {
constructor(type, variant, position, windowSize) { constructor(type, variant, position, windowSize, distance = null) {
this.type = type; // e.g. 'pressure', 'flow', etc. this.type = type; // e.g. 'pressure', 'flow', etc.
this.variant = variant; // e.g. 'predicted' or 'measured', etc.. this.variant = variant; // e.g. 'predicted' or 'measured', etc..
this.position = position; // Downstream or upstream of parent object this.position = position; // Downstream or upstream of parent object
this.windowSize = windowSize; // Rolling window size this.windowSize = windowSize; // Rolling window size
this.distance = distance; // Distance from parent, if applicable
// Place all data inside an array // Place all data inside an array
this.values = []; // Array to store all values this.values = []; // Array to store all values
@@ -36,13 +37,12 @@ class Measurement {
return this; return this;
} }
setDistance(distance) {
this.distance = distance;
return this;
}
setValue(value, timestamp = Date.now()) { 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 //shift the oldest value
if(this.values.length >= this.windowSize){ if(this.values.length >= this.windowSize){
@@ -168,7 +168,8 @@ class Measurement {
this.type, this.type,
this.variant, this.variant,
this.position, this.position,
this.windowSize this.windowSize,
this.distance
); );
// Copy values and timestamps // Copy values and timestamps

View File

@@ -5,6 +5,7 @@ class MeasurementBuilder {
this.type = null; this.type = null;
this.variant = null; this.variant = null;
this.position = null; this.position = null;
this.distance = null;
this.windowSize = 10; // Default window size this.windowSize = 10; // Default window size
} }
@@ -32,6 +33,11 @@ class MeasurementBuilder {
return this; return this;
} }
setDistance(distance) {
this.distance = distance;
return this;
}
build() { build() {
// Validate required fields // Validate required fields
if (!this.type) { if (!this.type) {
@@ -43,12 +49,14 @@ class MeasurementBuilder {
if (!this.position) { if (!this.position) {
throw new Error('Measurement position is required'); throw new Error('Measurement position is required');
} }
// distance is not a requirement as it can be derived from position
return new Measurement( return new Measurement(
this.type, this.type,
this.variant, this.variant,
this.position, this.position,
this.windowSize this.windowSize,
this.distance
); );

View File

@@ -3,15 +3,16 @@ const EventEmitter = require('events');
const convertModule = require('../convert/index'); const convertModule = require('../convert/index');
class MeasurementContainer { class MeasurementContainer {
constructor(options = {}) { constructor(options = {},logger) {
this.emitter = new EventEmitter(); this.emitter = new EventEmitter();
this.measurements = {}; this.measurements = {};
this.windowSize = options.windowSize || 10; // Default window size this.windowSize = options.windowSize || 10; // Default window size
// For chaining context // For chaining context
this._currentType = null; this._currentType = null;
this._currentVariant = null; this._currentVariant = null;
this._currentPosition = null; this._currentPosition = null;
this._currentDistance = null;
this._unit = null; this._unit = null;
// Default units for each measurement type // Default units for each measurement type
@@ -24,11 +25,11 @@ class MeasurementContainer {
length: 'm', length: 'm',
...options.defaultUnits // Allow override ...options.defaultUnits // Allow override
}; };
// Auto-conversion settings // Auto-conversion settings
this.autoConvert = options.autoConvert !== false; // Default to true this.autoConvert = options.autoConvert !== false; // Default to true
this.preferredUnits = options.preferredUnits || {}; // Per-measurement overrides this.preferredUnits = options.preferredUnits || {}; // Per-measurement overrides
// For chaining context // For chaining context
this._currentType = null; this._currentType = null;
this._currentVariant = null; this._currentVariant = null;
@@ -39,23 +40,23 @@ class MeasurementContainer {
this.childId = null; this.childId = null;
this.childName = null; this.childName = null;
this.parentRef = null; this.parentRef = null;
} }
// NEW: Methods to set child context // NEW: Methods to set child context
setChildId(childId) { setChildId(childId) {
this.childId = childId; this.childId = childId;
return this; return this;
} }
setChildName(childName) { setChildName(childName) {
this.childName = childName; this.childName = childName;
return this; return this;
} }
setParentRef(parent) { setParentRef(parent) {
this.parentRef = parent; this.parentRef = parent;
return this; return this;
} }
// New method to set preferred units // New method to set preferred units
@@ -66,9 +67,9 @@ class MeasurementContainer {
// Get the target unit for a measurement type // Get the target unit for a measurement type
_getTargetUnit(measurementType) { _getTargetUnit(measurementType) {
return this.preferredUnits[measurementType] || return this.preferredUnits[measurementType] ||
this.defaultUnits[measurementType] || this.defaultUnits[measurementType] ||
null; null;
} }
// Chainable methods // Chainable methods
@@ -93,78 +94,84 @@ class MeasurementContainer {
throw new Error('Variant must be specified before position'); throw new Error('Variant must be specified before position');
} }
// Turn string positions into numeric values this._currentPosition = positionValue;
if (typeof positionValue == "string") {
positionValue = this._convertPositionStr2Num(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; return this;
} }
// ENHANCED: Update your existing value method // ENHANCED: Update your existing value method
value(val, timestamp = Date.now(), sourceUnit = null) { 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(); // Auto-convert if enabled and units are specified
const targetUnit = this._getTargetUnit(this._currentType); if (this.autoConvert && sourceUnit && targetUnit && sourceUnit !== targetUnit) {
try {
let convertedValue = val; convertedValue = convertModule(val).from(sourceUnit).to(targetUnit);
let finalUnit = sourceUnit || targetUnit; finalUnit = targetUnit;
// Auto-convert if enabled and units are specified if (this.logger) {
if (this.autoConvert && sourceUnit && targetUnit && sourceUnit !== targetUnit) { this.logger.debug(`Auto-converted ${val} ${sourceUnit} to ${convertedValue} ${targetUnit}`);
try { }
convertedValue = convertModule(val).from(sourceUnit).to(targetUnit); } catch (error) {
finalUnit = targetUnit; if (this.logger) {
this.logger.warn(`Auto-conversion failed from ${sourceUnit} to ${targetUnit}: ${error.message}`);
if (this.logger) { }
this.logger.debug(`Auto-converted ${val} ${sourceUnit} to ${convertedValue} ${targetUnit}`); 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) { // ENHANCED: Emit event with rich context
measurement.setUnit(finalUnit); 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 // Emit the exact event your parent expects
const eventData = { this.emitter.emit(`${this._currentType}.${this._currentVariant}.${this._currentPosition}`, eventData);
value: convertedValue, console.log(`Emitted event: ${this._currentType}.${this._currentVariant}.${this._currentPosition}`, eventData);
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 return this;
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;
} }
unit(unitName) { unit(unitName) {
if (!this._ensureChainIsValid()) return this; if (!this._ensureChainIsValid()) return this;
const measurement = this._getOrCreateMeasurement(); const measurement = this._getOrCreateMeasurement();
measurement.setUnit(unitName); measurement.setUnit(unitName);
this._unit = unitName; this._unit = unitName;
@@ -180,7 +187,7 @@ class MeasurementContainer {
getCurrentValue(requestedUnit = null) { getCurrentValue(requestedUnit = null) {
const measurement = this.get(); const measurement = this.get();
if (!measurement) return null; if (!measurement) return null;
const value = measurement.getCurrentValue(); const value = measurement.getCurrentValue();
if (value === null) return null; if (value === null) return null;
@@ -207,7 +214,7 @@ class MeasurementContainer {
getAverage(requestedUnit = null) { getAverage(requestedUnit = null) {
const measurement = this.get(); const measurement = this.get();
if (!measurement) return null; if (!measurement) return null;
const avgValue = measurement.getAverage(); const avgValue = measurement.getAverage();
if (avgValue === null) return null; if (avgValue === null) return null;
@@ -244,34 +251,24 @@ class MeasurementContainer {
// Difference calculations between positions // Difference calculations between positions
difference(requestedUnit = null) { difference(requestedUnit = null) {
if (!this._currentType || !this._currentVariant) { if (!this._currentType || !this._currentVariant) {
throw new Error('Type and variant must be specified for difference calculation'); throw new Error('Type and variant must be specified for difference calculation');
} }
const upstream = this.measurements?.[this._currentType]?.[this._currentVariant]?.['upstream'] || null;
const savedPosition = this._currentPosition; const downstream = this.measurements?.[this._currentType]?.[this._currentVariant]?.['downstream'] || null;
// 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;
if (!upstream || !downstream || upstream.values.length === 0 || downstream.values.length === 0) { if (!upstream || !downstream || upstream.values.length === 0 || downstream.values.length === 0) {
return null; return null;
} }
// Get target unit for conversion // Get target unit for conversion
const targetUnit = requestedUnit || upstream.unit || downstream.unit; const targetUnit = requestedUnit || upstream.unit || downstream.unit;
// Get values in the same unit // Get values in the same unit
const upstreamValue = this._convertValueToUnit(upstream.getCurrentValue(), upstream.unit, targetUnit); const upstreamValue = this._convertValueToUnit(upstream.getCurrentValue(), upstream.unit, targetUnit);
const downstreamValue = this._convertValueToUnit(downstream.getCurrentValue(), downstream.unit, targetUnit); const downstreamValue = this._convertValueToUnit(downstream.getCurrentValue(), downstream.unit, targetUnit);
const upstreamAvg = this._convertValueToUnit(upstream.getAverage(), upstream.unit, targetUnit); const upstreamAvg = this._convertValueToUnit(upstream.getAverage(), upstream.unit, targetUnit);
const downstreamAvg = this._convertValueToUnit(downstream.getAverage(), downstream.unit, targetUnit); const downstreamAvg = this._convertValueToUnit(downstream.getAverage(), downstream.unit, targetUnit);
@@ -298,21 +295,22 @@ class MeasurementContainer {
if (!this.measurements[this._currentType]) { if (!this.measurements[this._currentType]) {
this.measurements[this._currentType] = {}; this.measurements[this._currentType] = {};
} }
if (!this.measurements[this._currentType][this._currentVariant]) { if (!this.measurements[this._currentType][this._currentVariant]) {
this.measurements[this._currentType][this._currentVariant] = {}; this.measurements[this._currentType][this._currentVariant] = {};
} }
if (!this.measurements[this._currentType][this._currentVariant][this._currentPosition]) { 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() new MeasurementBuilder()
.setType(this._currentType) .setType(this._currentType)
.setVariant(this._currentVariant) .setVariant(this._currentVariant)
.setPosition(this._currentPosition) .setPosition(this._currentPosition)
.setWindowSize(this.windowSize) .setWindowSize(this.windowSize)
.setDistance(this._currentDistance)
.build(); .build();
} }
return this.measurements[this._currentType][this._currentVariant][this._currentPosition]; return this.measurements[this._currentType][this._currentVariant][this._currentPosition];
} }
@@ -325,25 +323,21 @@ class MeasurementContainer {
if (!this._currentType) { if (!this._currentType) {
throw new Error('Type must be specified before listing variants'); 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]) : []; Object.keys(this.measurements[this._currentType]) : [];
} }
getPositions(asNumber = false) { getPositions() {
if (!this._currentType || !this._currentVariant) { if (!this._currentType || !this._currentVariant) {
throw new Error('Type and variant must be specified before listing positions'); throw new Error('Type and variant must be specified before listing positions');
} }
if (!this.measurements[this._currentType] || if (!this.measurements[this._currentType] ||
!this.measurements[this._currentType][this._currentVariant]) { !this.measurements[this._currentType][this._currentVariant]) {
return []; return [];
} }
if (asNumber) { return Object.keys(this.measurements[this._currentType][this._currentVariant]);
return Object.keys(this.measurements[this._currentType][this._currentVariant]);
}
return Object.keys(this.measurements[this._currentType][this._currentVariant]).map(this._convertPositionNum2Str);
} }
clear() { clear() {
@@ -408,7 +402,7 @@ class MeasurementContainer {
const best = convertModule(currentValue) const best = convertModule(currentValue)
.from(measurement.unit) .from(measurement.unit)
.toBest({ exclude: excludeUnits }); .toBest({ exclude: excludeUnits });
return best; return best;
} catch (error) { } catch (error) {
if (this.logger) { if (this.logger) {
@@ -419,16 +413,14 @@ class MeasurementContainer {
} }
_convertPositionStr2Num(positionString) { _convertPositionStr2Num(positionString) {
switch(positionString) {
switch (positionString) {
case "atEquipment": case "atEquipment":
return 0; return 0;
case "upstream": case "upstream":
return Number.POSITIVE_INFINITY; return Number.POSITIVE_INFINITY;
case "downstream": case "downstream":
return Number.NEGATIVE_INFINITY; return Number.NEGATIVE_INFINITY;
default: default:
if (this.logger) { if (this.logger) {
this.logger.error(`Invalid positionVsParent provided: ${positionString}`); this.logger.error(`Invalid positionVsParent provided: ${positionString}`);
@@ -438,18 +430,15 @@ class MeasurementContainer {
} }
_convertPositionNum2Str(positionValue) { _convertPositionNum2Str(positionValue) {
if (positionValue === 0) { switch (positionValue) {
case 0:
return "atEquipment"; return "atEquipment";
} case (positionValue < 0):
if (positionValue < 0) {
return "upstream"; return "upstream";
} case (positionValue > 0):
if (positionValue > 0) {
return "downstream"; return "downstream";
} default:
console.log(`Invalid position provided: ${positionValue}`);
if (this.logger) {
this.logger.error(`Invalid position provided: ${positionValue}`);
} }
} }

View File

@@ -7,249 +7,325 @@ console.log('retrieving, and converting measurement data with automatic unit han
// ==================================== // ====================================
// BASIC SETUP EXAMPLES // BASIC SETUP EXAMPLES
// ==================================== // ====================================
console.log('--- Example 1: Basic Setup & Event Subscription ---'); console.log('--- Example 1: Basic Setup & Distance ---');
// Create a basic container // Create a basic container
const basicContainer = new MeasurementContainer({ windowSize: 20 }); 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) => { 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) => { 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 // Basic value setting with distance
console.log('Setting basic pressure values...'); console.log('\nSetting pressure values with distances:');
basicContainer.type('pressure').variant('measured').position('upstream').value(100).unit('psi'); basicContainer
basicContainer.type('pressure').variant('measured').position('downstream').value(95).unit('psi'); .type('pressure')
basicContainer.type('pressure').variant('measured').position('downstream').value(80); // Additional value .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'); 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('--- 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({ const autoContainer = new MeasurementContainer({
autoConvert: true, autoConvert: true,
windowSize: 50, windowSize: 50,
defaultUnits: { defaultUnits: {
pressure: 'bar', // Default pressure unit pressure: 'bar',
flow: 'l/min', // Default flow unit flow: 'l/min',
power: 'kW', // Default power unit power: 'kW',
temperature: 'C' // Default temperature unit temperature: 'C'
}, },
preferredUnits: { preferredUnits: {
pressure: 'psi' // Override: store pressure in PSI instead of bar pressure: 'psi'
} }
}); });
// Values are automatically converted to preferred units // Values automatically convert to preferred units
console.log('Adding pressure data with auto-conversion:'); console.log('Adding pressure with auto-conversion:');
autoContainer.type('pressure').variant('measured').position('upstream') autoContainer
.value(1.5, Date.now(), 'bar'); // Input: 1.5 bar → Auto-stored as ~21.76 psi .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') const converted = autoContainer
.value(20, Date.now(), 'psi'); // Input: 20 psi → Stored as 20 psi (already in preferred unit) .type('pressure')
.variant('measured')
.position('upstream')
.get();
// Check what was actually stored console.log(`Stored as: ${converted.getCurrentValue()} ${converted.unit} (distance=${converted.distance}m)`);
const storedPressure = autoContainer.type('pressure').variant('measured').position('upstream').get(); console.log('✅ Auto-conversion complete\n');
console.log(` Stored upstream pressure: ${storedPressure.getCurrentValue()} ${storedPressure.unit}`);
console.log(' Auto-conversion setup complete\n');
// ==================================== // ====================================
// UNIT CONVERSION EXAMPLES // UNIT CONVERSION ON RETRIEVAL
// ==================================== // ====================================
console.log('--- Example 3: 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
autoContainer.type('flow').variant('predicted').position('upstream') .type('flow')
.value(100, Date.now(), 'l/min'); // Stored in l/min (default) .variant('predicted')
.position('upstream')
.distance(2.4)
.value(100, Date.now(), 'l/min');
autoContainer.type('flow').variant('predicted').position('downstream') const flowMeasurement = autoContainer
.value(6, Date.now(), 'm3/h'); // Auto-converted from m3/h to l/min .type('flow')
.variant('predicted')
.position('upstream')
.get();
// Retrieve the same data in different units console.log(`Flow in l/min: ${flowMeasurement.getCurrentValue('l/min')}`);
const flowLPM = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('l/min'); console.log(`Flow in m³/h: ${flowMeasurement.getCurrentValue('m3/h').toFixed(2)}`);
const flowM3H = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('m3/h'); console.log(`Flow in gal/min: ${flowMeasurement.getCurrentValue('gal/min').toFixed(2)}`);
const flowGPM = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('gal/min'); console.log(`Distance: ${flowMeasurement.distance}m\n`);
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');
// ==================================== // ====================================
// SMART UNIT SELECTION // SMART UNIT SELECTION
// ==================================== // ====================================
console.log('--- Example 4: 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
autoContainer.type('pressure').variant('test').position('sensor') .type('pressure')
.variant('test')
.position('sensor')
.distance(0.2)
.value(0.001, Date.now(), 'bar'); .value(0.001, Date.now(), 'bar');
// Get the best unit for this small value const bestUnit = autoContainer
const bestUnit = autoContainer.type('pressure').variant('test').position('sensor').getBestUnit(); .type('pressure')
.variant('test')
.position('sensor')
.getBestUnit();
if (bestUnit) { 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'); const availableUnits = autoContainer.getAvailableUnits('pressure');
console.log(`Available pressure units: ${availableUnits.slice(0, 8).join(', ')}... (${availableUnits.length} total)`); console.log(`Available units: ${availableUnits.slice(0, 5).join(', ')}...\n`);
console.log('Smart unit selection complete\n');
// ==================================== // ====================================
// BASIC RETRIEVAL AND CALCULATIONS // BASIC RETRIEVAL
// ==================================== // ====================================
console.log('--- Example 5: Basic Value Retrieval ---'); console.log('--- Example 5: Basic Value Retrieval ---');
console.log('Getting individual values and their units...\n');
// Using basic container for clear examples const upstreamVal = basicContainer
const upstreamValue = basicContainer.type('pressure').variant('measured').position('upstream').getCurrentValue(); .type('pressure')
const upstreamUnit = basicContainer.type('pressure').variant('measured').position('upstream').get().unit; .variant('measured')
console.log(`Upstream pressure: ${upstreamValue} ${upstreamUnit}`); .position('upstream')
.getCurrentValue();
const downstreamValue = basicContainer.type('pressure').variant('measured').position('downstream').getCurrentValue(); const upstreamData = basicContainer
const downstreamUnit = basicContainer.type('pressure').variant('measured').position('downstream').get().unit; .type('pressure')
console.log(`Downstream pressure: ${downstreamValue} ${downstreamUnit}`); .variant('measured')
console.log('Basic retrieval complete\n'); .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('--- Example 6: Calculations & Statistics ---');
console.log('Using built-in calculation methods...\n');
// Add flow data for calculations basicContainer
basicContainer.type('flow').variant('predicted').position('upstream').value(200).unit('gpm'); .type('flow')
basicContainer.type('flow').variant('predicted').position('downstream').value(195).unit('gpm'); .variant('predicted')
.position('upstream')
.distance(3.0)
.value(200)
.unit('gpm');
const flowAvg = basicContainer.type('flow').variant('predicted').position('upstream').getAverage(); basicContainer
console.log(`Average upstream flow: ${flowAvg} gpm`); .type('flow')
.variant('predicted')
.position('downstream')
.distance(8.5)
.value(195)
.unit('gpm');
// Calculate pressure difference between upstream and downstream const flowAvg = basicContainer
const pressureDiff = basicContainer.type('pressure').variant('measured').difference(); .type('flow')
console.log(`Pressure difference: ${pressureDiff.value} ${pressureDiff.unit}`); .variant('predicted')
console.log('Calculations complete\n'); .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('--- Example 7: Advanced Statistics & History ---');
console.log('Adding multiple values and getting comprehensive statistics...\n');
// Add several flow measurements to build history basicContainer
basicContainer.type('flow').variant('measured').position('upstream') .type('flow')
.value(210).value(215).value(205).value(220).value(200).unit('m3/h'); .variant('measured')
basicContainer.type('flow').variant('measured').position('downstream') .position('upstream')
.value(190).value(195).value(185).value(200).value(180).unit('m3/h'); .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('Flow Statistics:');
console.log(`- Current value: ${measurement.getCurrentValue()} ${measurement.get().unit}`); console.log(` Current: ${stats.getCurrentValue()} ${statsData.unit}`);
console.log(`- Average: ${measurement.getAverage().toFixed(1)} ${measurement.get().unit}`); console.log(` Average: ${stats.getAverage().toFixed(1)} ${statsData.unit}`);
console.log(`- Minimum: ${measurement.getMin()} ${measurement.get().unit}`); console.log(` Min: ${stats.getMin()} ${statsData.unit}`);
console.log(`- Maximum: ${measurement.getMax()} ${measurement.get().unit}`); console.log(` Max: ${stats.getMax()} ${statsData.unit}`);
console.log(` Distance: ${statsData.distance}m`);
// Show all values with timestamps const allValues = stats.getAllValues();
const allValues = measurement.getAllValues(); console.log(` Samples: ${allValues.values.length}`);
console.log(`- Total samples: ${allValues.values.length}`); console.log(` History: [${allValues.values.join(', ')}]\n`);
console.log(`- Value history: [${allValues.values.join(', ')}]`);
console.log('Advanced statistics complete\n');
// ==================================== // ====================================
// DYNAMIC UNIT MANAGEMENT // DYNAMIC UNIT MANAGEMENT
// ==================================== // ====================================
console.log('--- Example 8: 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'); autoContainer.setPreferredUnit('flow', 'm3/h');
console.log('Changed preferred flow unit to m³/h'); console.log('Changed preferred flow unit to m³/h');
// Add new flow data - will auto-convert to new preferred unit autoContainer
autoContainer.type('flow').variant('realtime').position('inlet') .type('flow')
.value(150, Date.now(), 'l/min'); // Input in l/min, stored as m³/h .variant('realtime')
.position('inlet')
.distance(1.2)
.value(150, Date.now(), 'l/min');
const realtimeFlow = autoContainer.type('flow').variant('realtime').position('inlet'); const realtimeFlow = autoContainer
console.log(`Stored as: ${realtimeFlow.getCurrentValue()} ${realtimeFlow.get().unit}`); .type('flow')
console.log(`Original unit: ${realtimeFlow.getCurrentValue('l/min')} l/min`); .variant('realtime')
console.log('Dynamic unit management complete\n'); .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 // DATA EXPLORATION
// ==================================== // ====================================
console.log('--- Example 9: 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('Pressure variants:', basicContainer.type('pressure').getVariants());
console.log('Measured pressure positions:', basicContainer.type('pressure').variant('measured').getPositions()); console.log('Measured pressure positions:', basicContainer.type('pressure').variant('measured').getPositions());
// Show data structure overview console.log('\nData Structure:');
console.log('\nData Structure Overview:');
basicContainer.getTypes().forEach(type => { basicContainer.getTypes().forEach(type => {
console.log(`${type.toUpperCase()}:`);
const variants = basicContainer.type(type).getVariants(); const variants = basicContainer.type(type).getVariants();
variants.forEach(variant => { if (variants.length > 0) {
const positions = basicContainer.type(type).variant(variant).getPositions(); console.log(`${type.toUpperCase()}:`);
positions.forEach(position => { variants.forEach(variant => {
const measurement = basicContainer.type(type).variant(variant).position(position).get(); const positions = basicContainer.type(type).variant(variant).getPositions();
if (measurement && measurement.values.length > 0) { positions.forEach(position => {
console.log(` └── ${variant}.${position}: ${measurement.values.length} values (${measurement.unit || 'no unit'})`); 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 Summary ---\n');
console.log('BEST PRACTICES FOR NEW USERS:\n');
console.log('1. SETUP:'); console.log('SETUP:');
console.log(' • Enable auto-conversion for consistent units'); console.log(' • Enable autoConvert for consistent units');
console.log(' • Define default units for your measurement types'); console.log(' • Define defaultUnits for your measurement types');
console.log(' • Set appropriate window size for your data needs\n'); console.log(' • Set windowSize based on your data retention needs\n');
console.log('2. STORING DATA:'); console.log('STORING DATA:');
console.log(' Always use the full chain: type().variant().position().value()'); console.log(' • Chain methods: type().variant().position().distance().value()');
console.log(' • Specify source unit when adding values: .value(100, timestamp, "psi")'); console.log(' • Set distance once - it persists for that position');
console.log(' • Set units immediately after first value: .value(100).unit("psi")\n'); 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('RETRIEVING DATA:');
console.log(' • Use .getCurrentValue("unit") to get values in specific units'); console.log(' • Use .getCurrentValue("unit") for specific units');
console.log(' • Use .getBestUnit() for automatic unit selection'); console.log(' • Use .getBestUnit() for automatic selection');
console.log(' • Use .difference() for automatic upstream/downstream calculations\n'); console.log(' • Use .difference() for upstream/downstream deltas');
console.log(' • Access .get().distance for physical positioning\n');
console.log('4. MONITORING:'); console.log('MONITORING:');
console.log(' • Subscribe to events for real-time updates'); console.log(' • Subscribe: .emitter.on("type.variant.position", callback)');
console.log(' Use .emitter.on("type.variant.position", callback)'); console.log(' • Event data includes: value, unit, timestamp, distance');
console.log(' • Explore available data with .getTypes(), .getVariants(), .getPositions()\n'); console.log(' • Explore data: .getTypes(), .getVariants(), .getPositions()\n');
console.log('All examples complete! Ready to use MeasurementContainer'); module.exports = { basicContainer, autoContainer };
// 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
};