license update and enhancements to measurement functionality + child parent relationship

This commit is contained in:
znetsixe
2025-08-07 13:52:29 +02:00
parent 7061d6a539
commit e87f9da4bf
9 changed files with 1862 additions and 363 deletions

View File

@@ -50,6 +50,8 @@ class MeasurementBuilder {
this.position,
this.windowSize
);
}
}

View File

@@ -1,8 +1,10 @@
const MeasurementBuilder = require('./MeasurementBuilder');
const EventEmitter = require('events');
const convertModule = require('../convert/index');
class MeasurementContainer {
constructor(options = {}, logger) {
this.logger = logger;
constructor(options = {}) {
this.emitter = new EventEmitter();
this.measurements = {};
this.windowSize = options.windowSize || 10; // Default window size
@@ -10,6 +12,63 @@ class MeasurementContainer {
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
@@ -37,20 +96,69 @@ class MeasurementContainer {
return this;
}
// Core methods that complete the chain
value(val, timestamp = Date.now()) {
if (!this._ensureChainIsValid()) return this;
const measurement = this._getOrCreateMeasurement();
measurement.setValue(val, timestamp);
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;
}
@@ -60,14 +168,52 @@ class MeasurementContainer {
return this._getOrCreateMeasurement();
}
getCurrentValue() {
getCurrentValue(requestedUnit = null) {
const measurement = this.get();
return measurement ? measurement.getCurrentValue() : null;
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() {
getAverage(requestedUnit = null) {
const measurement = this.get();
return measurement ? measurement.getAverage() : null;
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() {
@@ -85,47 +231,43 @@ class MeasurementContainer {
return measurement ? measurement.getAllValues() : null;
}
// Difference calculations between positions
difference() {
difference(requestedUnit = null) {
if (!this._currentType || !this._currentVariant) {
throw new Error('Type and variant must be specified for difference calculation');
}
// Save position to restore chain state after operation
const savedPosition = this._currentPosition;
// Get upstream measurement
// Get upstream and downstream measurements
this._currentPosition = 'upstream';
const upstream = this.get();
// Get downstream measurement
this._currentPosition = 'downstream';
const downstream = this.get();
// Restore chain state
this._currentPosition = savedPosition;
if (!upstream || !downstream || upstream.values.length === 0 || downstream.values.length === 0) {
return null;
}
// Ensure units match
let downstreamForCalc = downstream;
if (upstream.unit && downstream.unit && upstream.unit !== downstream.unit) {
try {
downstreamForCalc = downstream.convertTo(upstream.unit);
} catch (error) {
if (this.logger) {
this.logger.error(`Unit conversion failed: ${error.message}`);
}
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: downstreamForCalc.getCurrentValue() - upstream.getCurrentValue() ,
avgDiff: downstreamForCalc.getAverage() - upstream.getAverage() ,
unit: upstream.unit
value: downstreamValue - upstreamValue,
avgDiff: downstreamAvg - upstreamAvg,
unit: targetUnit
};
}
@@ -195,6 +337,72 @@ class MeasurementContainer {
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;

View File

@@ -1,58 +1,255 @@
const { MeasurementContainer } = require('./index');
// Create a container
const container = new MeasurementContainer({ windowSize: 20 });
console.log('=== MEASUREMENT CONTAINER EXAMPLES ===\n');
console.log('This guide shows how to use the MeasurementContainer for storing,');
console.log('retrieving, and converting measurement data with automatic unit handling.\n');
// Example 1: Setting values with chaining
console.log('--- Example 1: Setting values ---');
container.type('pressure').variant('measured').position('upstream').value(100).unit('psi');
container.type('pressure').variant('measured').position('downstream').value(95).unit('psi');
container.type('pressure').variant('measured').position('downstream').value(80);
// ====================================
// BASIC SETUP EXAMPLES
// ====================================
console.log('--- Example 1: Basic Setup & Event Subscription ---');
// Example 2: Getting values with chaining
console.log('--- Example 2: Getting values ---');
const upstreamValue = container.type('pressure').variant('measured').position('upstream').getCurrentValue();
const upstreamUnit = container.type('pressure').variant('measured').position('upstream').get().unit;
// Create a basic container
const basicContainer = new MeasurementContainer({ windowSize: 20 });
// Subscribe to flow 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()}`);
});
//show all flow values from variant measured
basicContainer.emitter.on('flow.measured.*', (data) => {
console.log(`📡 Event---------- I DID IT: Flow measured ${data.position} update: ${data.value}`)
});
// 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
console.log('✅ Basic setup complete\n');
// ====================================
// AUTO-CONVERSION SETUP EXAMPLES
// ====================================
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
},
preferredUnits: {
pressure: 'psi' // Override: store pressure in PSI instead of bar
}
});
// 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
autoContainer.type('pressure').variant('measured').position('downstream')
.value(20, Date.now(), 'psi'); // Input: 20 psi → Stored as 20 psi (already in preferred unit)
// 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');
// ====================================
// UNIT CONVERSION EXAMPLES
// ====================================
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('downstream')
.value(6, Date.now(), 'm3/h'); // Auto-converted from m3/h to l/min
// 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');
// ====================================
// 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')
.value(0.001, Date.now(), 'bar');
// Get the best unit for this small value
const bestUnit = autoContainer.type('pressure').variant('test').position('sensor').getBestUnit();
if (bestUnit) {
console.log(`Best unit representation: ${bestUnit.val} ${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');
// ====================================
// BASIC RETRIEVAL AND CALCULATIONS
// ====================================
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 downstreamValue = container.type('pressure').variant('measured').position('downstream').getCurrentValue();
const downstreamUnit = container.type('pressure').variant('measured').position('downstream').get().unit;
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');
// Example 3: Calculations using chained methods
console.log('--- Example 3: Calculations ---');
container.type('flow').variant('predicted').position('upstream').value(200).unit('gpm');
container.type('flow').variant('predicted').position('downstream').value(195).unit('gpm');
// ====================================
// CALCULATIONS AND STATISTICS
// ====================================
console.log('--- Example 6: Calculations & Statistics ---');
console.log('Using built-in calculation methods...\n');
const flowAvg = container.type('flow').variant('predicted').position('upstream').getAverage();
// 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');
const flowAvg = basicContainer.type('flow').variant('predicted').position('upstream').getAverage();
console.log(`Average upstream flow: ${flowAvg} gpm`);
// Example 4: Getting pressure difference
console.log('--- Example 4: Difference calculations ---');
const pressureDiff = container.type('pressure').variant('measured').difference();
// 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');
// Example 5: Adding multiple values to track history
console.log('--- Example 5: Multiple values ---');
// Add several values to upstream flow
container.type('flow').variant('measured').position('upstream')
.value(210).value(215).value(205).unit('gpm');
// ====================================
// ADVANCED STATISTICS
// ====================================
console.log('--- Example 7: Advanced Statistics & History ---');
console.log('Adding multiple values and getting comprehensive statistics...\n');
// Then get statistics
console.log('Flow statistics:');
console.log(`- Current: ${container.type('flow').variant('measured').position('upstream').getCurrentValue()} gpm`);
console.log(`- Average: ${container.type('flow').variant('measured').position('upstream').getAverage()} gpm`);
console.log(`- Min: ${container.type('flow').variant('measured').position('upstream').getMin()} gpm`);
console.log(`- Max: ${container.type('flow').variant('measured').position('upstream').getMax()} gpm`);
console.log(`Show all values : ${JSON.stringify(container.type('flow').variant('measured').position('upstream').getAllValues())}`);
// 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');
// Example 6: Listing available data
console.log('--- Example 6: Listing available data ---');
console.log('Types:', container.getTypes());
console.log('Pressure variants:', container.type('pressure').getVariants());
console.log('Measured pressure positions:', container.type('pressure').variant('measured').getPositions());
// 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}`);
// 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');
// ====================================
// 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
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');
// ====================================
// 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('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:');
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'})`);
}
});
});
});
console.log('Data exploration complete\n');
// ====================================
// BEST PRACTICES SUMMARY
// ====================================
console.log('--- Best Practices Summary ---');
console.log('BEST PRACTICES FOR NEW USERS:\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('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('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('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('All examples complete! Ready to use MeasurementContainer');
// Export for programmatic use
module.exports = {
runExamples: () => {
console.log('Examples of the measurement chainable API');
}
};
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
};