changed the folder and added index.js
This commit is contained in:
187
src/measurements/Measurement.js
Normal file
187
src/measurements/Measurement.js
Normal file
@@ -0,0 +1,187 @@
|
||||
// Add unit conversion support
|
||||
const convertModule = require('../../../convert/dependencies/index');
|
||||
|
||||
class Measurement {
|
||||
constructor(type, variant, position, windowSize) {
|
||||
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
|
||||
|
||||
// Place all data inside an array
|
||||
this.values = []; // Array to store all values
|
||||
this.timestamps = []; // Array to store all timestamps
|
||||
|
||||
// Unit tracking
|
||||
this.unit = null; // Current unit of measurement
|
||||
|
||||
// For tracking differences if this is a calculated difference measurement
|
||||
this.isDifference = false;
|
||||
this.sourcePositions = [];
|
||||
}
|
||||
|
||||
// -- Updater methods --
|
||||
setType(type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
setVariant(variant) {
|
||||
this.variant = variant;
|
||||
return this;
|
||||
}
|
||||
|
||||
setPosition(position) {
|
||||
this.position = position;
|
||||
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){
|
||||
this.values.shift();
|
||||
this.timestamps.shift();
|
||||
}
|
||||
|
||||
//push the new value
|
||||
this.values.push(value);
|
||||
this.timestamps.push(timestamp);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setUnit(unit) {
|
||||
this.unit = unit;
|
||||
return this;
|
||||
}
|
||||
|
||||
// -- Getter methods --
|
||||
getCurrentValue() {
|
||||
if (this.values.length === 0) return null;
|
||||
return this.values[this.values.length - 1];
|
||||
}
|
||||
|
||||
getAverage() {
|
||||
if (this.values.length === 0) return null;
|
||||
const sum = this.values.reduce((acc, val) => acc + val, 0);
|
||||
return sum / this.values.length;
|
||||
}
|
||||
|
||||
getMin() {
|
||||
if (this.values.length === 0) return null;
|
||||
return Math.min(...this.values);
|
||||
}
|
||||
|
||||
getMax() {
|
||||
if (this.values.length === 0) return null;
|
||||
return Math.max(...this.values);
|
||||
}
|
||||
|
||||
getAllValues() {
|
||||
return {
|
||||
values: [...this.values],
|
||||
timestamps: [...this.timestamps],
|
||||
unit: this.unit
|
||||
};
|
||||
}
|
||||
|
||||
// -- Position-based methods --
|
||||
|
||||
// Create a new measurement that is the difference between two positions
|
||||
static createDifference(upstreamMeasurement, downstreamMeasurement) {
|
||||
console.log('hello:');
|
||||
if (upstreamMeasurement.type !== downstreamMeasurement.type ||
|
||||
upstreamMeasurement.variant !== downstreamMeasurement.variant) {
|
||||
throw new Error('Cannot calculate difference between different measurement types or variants');
|
||||
}
|
||||
|
||||
// Ensure units match
|
||||
let downstream = downstreamMeasurement;
|
||||
if (upstreamMeasurement.unit && downstream.unit &&
|
||||
upstreamMeasurement.unit !== downstream.unit) {
|
||||
downstream = downstream.convertTo(upstreamMeasurement.unit);
|
||||
}
|
||||
|
||||
// Create a new difference measurement
|
||||
const diffMeasurement = new Measurement(
|
||||
upstreamMeasurement.type,
|
||||
upstreamMeasurement.variant,
|
||||
'difference',
|
||||
Math.min(upstreamMeasurement.windowSize, downstream.windowSize)
|
||||
);
|
||||
|
||||
// Mark as a difference measurement and keep track of sources
|
||||
diffMeasurement.isDifference = true;
|
||||
diffMeasurement.sourcePositions = ['upstream', 'downstream'];
|
||||
|
||||
// Calculate all differences where timestamps match
|
||||
const upValues = upstreamMeasurement.getAllValues();
|
||||
const downValues = downstream.getAllValues();
|
||||
|
||||
// Match timestamps and calculate differences
|
||||
for (let i = 0; i < upValues.timestamps.length; i++) {
|
||||
const upTimestamp = upValues.timestamps[i];
|
||||
const downIndex = downValues.timestamps.indexOf(upTimestamp);
|
||||
|
||||
if (downIndex !== -1) {
|
||||
|
||||
const diff = upValues.values[i] - downValues.values[downIndex];
|
||||
diffMeasurement.setValue(diff, upTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
diffMeasurement.setUnit(upstreamMeasurement.unit);
|
||||
|
||||
return diffMeasurement;
|
||||
}
|
||||
|
||||
// -- Additional getter methods --
|
||||
getLatestTimestamp() {
|
||||
if (this.timestamps.length === 0) return null;
|
||||
return this.timestamps[this.timestamps.length - 1];
|
||||
}
|
||||
|
||||
getValueAtTimestamp(timestamp) {
|
||||
const index = this.timestamps.indexOf(timestamp);
|
||||
if (index === -1) return null;
|
||||
return this.values[index];
|
||||
}
|
||||
|
||||
// -- Unit conversion methods --
|
||||
convertTo(targetUnit) {
|
||||
if (!this.unit) {
|
||||
throw new Error('Current unit not set, cannot convert');
|
||||
}
|
||||
|
||||
try {
|
||||
const convertedValues = this.values.map(value =>
|
||||
convertModule.convert(value).from(this.unit).to(targetUnit)
|
||||
);
|
||||
|
||||
const newMeasurement = new Measurement(
|
||||
this.type,
|
||||
this.variant,
|
||||
this.position,
|
||||
this.windowSize
|
||||
);
|
||||
|
||||
// Copy values and timestamps
|
||||
newMeasurement.values = convertedValues;
|
||||
newMeasurement.timestamps = [...this.timestamps];
|
||||
newMeasurement.unit = targetUnit;
|
||||
|
||||
return newMeasurement;
|
||||
} catch (error) {
|
||||
throw new Error(`Unit conversion failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Measurement;
|
||||
56
src/measurements/MeasurementBuilder.js
Normal file
56
src/measurements/MeasurementBuilder.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const Measurement = require('./Measurement');
|
||||
|
||||
class MeasurementBuilder {
|
||||
constructor() {
|
||||
this.type = null;
|
||||
this.variant = null;
|
||||
this.position = null;
|
||||
this.windowSize = 10; // Default window size
|
||||
}
|
||||
|
||||
// e.g. 'pressure', 'flow', etc.
|
||||
setType(type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
// e.g. 'predicted' or 'measured', etc..
|
||||
setVariant(variant) {
|
||||
this.variant = variant;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Downstream or upstream of parent object
|
||||
setPosition(position) {
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
|
||||
// default size of the data that gets tracked
|
||||
setWindowSize(windowSize) {
|
||||
this.windowSize = windowSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
build() {
|
||||
// Validate required fields
|
||||
if (!this.type) {
|
||||
throw new Error('Measurement type is required');
|
||||
}
|
||||
if (!this.variant) {
|
||||
throw new Error('Measurement variant is required');
|
||||
}
|
||||
if (!this.position) {
|
||||
throw new Error('Measurement position is required');
|
||||
}
|
||||
|
||||
return new Measurement(
|
||||
this.type,
|
||||
this.variant,
|
||||
this.position,
|
||||
this.windowSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MeasurementBuilder;
|
||||
200
src/measurements/MeasurementContainer.js
Normal file
200
src/measurements/MeasurementContainer.js
Normal file
@@ -0,0 +1,200 @@
|
||||
const MeasurementBuilder = require('./MeasurementBuilder');
|
||||
|
||||
class MeasurementContainer {
|
||||
constructor(options = {}, logger) {
|
||||
this.logger = logger;
|
||||
this.measurements = {};
|
||||
this.windowSize = options.windowSize || 10; // Default window size
|
||||
|
||||
// For chaining context
|
||||
this._currentType = null;
|
||||
this._currentVariant = null;
|
||||
this._currentPosition = null;
|
||||
}
|
||||
|
||||
// Chainable methods
|
||||
type(typeName) {
|
||||
this._currentType = typeName;
|
||||
this._currentVariant = null;
|
||||
this._currentPosition = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
variant(variantName) {
|
||||
if (!this._currentType) {
|
||||
throw new Error('Type must be specified before variant');
|
||||
}
|
||||
this._currentVariant = variantName;
|
||||
this._currentPosition = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
position(positionName) {
|
||||
if (!this._currentVariant) {
|
||||
throw new Error('Variant must be specified before position');
|
||||
}
|
||||
this._currentPosition = positionName;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
unit(unitName) {
|
||||
if (!this._ensureChainIsValid()) return this;
|
||||
|
||||
const measurement = this._getOrCreateMeasurement();
|
||||
measurement.setUnit(unitName);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Terminal operations - get data out
|
||||
get() {
|
||||
if (!this._ensureChainIsValid()) return null;
|
||||
return this._getOrCreateMeasurement();
|
||||
}
|
||||
|
||||
getCurrentValue() {
|
||||
const measurement = this.get();
|
||||
return measurement ? measurement.getCurrentValue() : null;
|
||||
}
|
||||
|
||||
getAverage() {
|
||||
const measurement = this.get();
|
||||
return measurement ? measurement.getAverage() : null;
|
||||
}
|
||||
|
||||
getMin() {
|
||||
const measurement = this.get();
|
||||
return measurement ? measurement.getMin() : null;
|
||||
}
|
||||
|
||||
getMax() {
|
||||
const measurement = this.get();
|
||||
return measurement ? measurement.getMax() : null;
|
||||
}
|
||||
|
||||
getAllValues() {
|
||||
const measurement = this.get();
|
||||
return measurement ? measurement.getAllValues() : null;
|
||||
}
|
||||
|
||||
// Difference calculations between positions
|
||||
difference() {
|
||||
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
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
value: downstreamForCalc.getCurrentValue() - upstream.getCurrentValue() ,
|
||||
avgDiff: downstreamForCalc.getAverage() - upstream.getAverage() ,
|
||||
unit: upstream.unit
|
||||
};
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
_ensureChainIsValid() {
|
||||
if (!this._currentType || !this._currentVariant || !this._currentPosition) {
|
||||
if (this.logger) {
|
||||
this.logger.error('Incomplete measurement chain, required: type, variant, and position');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_getOrCreateMeasurement() {
|
||||
// Initialize nested structure if needed
|
||||
if (!this.measurements[this._currentType]) {
|
||||
this.measurements[this._currentType] = {};
|
||||
}
|
||||
|
||||
if (!this.measurements[this._currentType][this._currentVariant]) {
|
||||
this.measurements[this._currentType][this._currentVariant] = {};
|
||||
}
|
||||
|
||||
if (!this.measurements[this._currentType][this._currentVariant][this._currentPosition]) {
|
||||
this.measurements[this._currentType][this._currentVariant][this._currentPosition] =
|
||||
new MeasurementBuilder()
|
||||
.setType(this._currentType)
|
||||
.setVariant(this._currentVariant)
|
||||
.setPosition(this._currentPosition)
|
||||
.setWindowSize(this.windowSize)
|
||||
.build();
|
||||
}
|
||||
|
||||
return this.measurements[this._currentType][this._currentVariant][this._currentPosition];
|
||||
}
|
||||
|
||||
// Additional utility methods
|
||||
getTypes() {
|
||||
return Object.keys(this.measurements);
|
||||
}
|
||||
|
||||
getVariants() {
|
||||
if (!this._currentType) {
|
||||
throw new Error('Type must be specified before listing variants');
|
||||
}
|
||||
return this.measurements[this._currentType] ?
|
||||
Object.keys(this.measurements[this._currentType]) : [];
|
||||
}
|
||||
|
||||
getPositions() {
|
||||
if (!this._currentType || !this._currentVariant) {
|
||||
throw new Error('Type and variant must be specified before listing positions');
|
||||
}
|
||||
|
||||
if (!this.measurements[this._currentType] ||
|
||||
!this.measurements[this._currentType][this._currentVariant]) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.keys(this.measurements[this._currentType][this._currentVariant]);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.measurements = {};
|
||||
this._currentType = null;
|
||||
this._currentVariant = null;
|
||||
this._currentPosition = null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MeasurementContainer;
|
||||
89
src/measurements/README.md
Normal file
89
src/measurements/README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Measurement System Documentation
|
||||
|
||||
This system provides a flexible way to store, retrieve, and analyze measurement data using a chainable API.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```javascript
|
||||
const { MeasurementContainer } = require('./index');
|
||||
const container = new MeasurementContainer({ windowSize: 20 });
|
||||
|
||||
// Set values
|
||||
container.type('pressure').variant('measured').position('upstream').value(100).unit('psi');
|
||||
|
||||
// Get values
|
||||
const upstreamPressure = container.type('pressure').variant('measured').position('upstream').getCurrentValue();
|
||||
console.log(`Upstream pressure: ${upstreamPressure}`);
|
||||
```
|
||||
|
||||
## Chainable API Methods
|
||||
|
||||
### Setting Context
|
||||
- `type(typeName)` - Set the measurement type (pressure, flow, etc.)
|
||||
- `variant(variantName)` - Set the variant (measured, predicted, etc.)
|
||||
- `position(positionName)` - Set the position (upstream, downstream, etc.)
|
||||
|
||||
### Setting Data
|
||||
- `value(val, [timestamp])` - Add a value with optional timestamp
|
||||
- `unit(unitName)` - Set the measurement unit
|
||||
|
||||
### Getting Data
|
||||
- `get()` - Get the measurement object
|
||||
- `getCurrentValue()` - Get the most recent value
|
||||
- `getAverage()` - Calculate average of all values
|
||||
- `getMin()` - Get minimum value
|
||||
- `getMax()` - Get maximum value
|
||||
|
||||
### Calculations
|
||||
- `difference()` - Calculate difference between upstream and downstream positions
|
||||
|
||||
### Listing Available Data
|
||||
- `getTypes()` - Get all measurement types
|
||||
- `listVariants()` - List variants for current type
|
||||
- `listPositions()` - List positions for current type and variant
|
||||
|
||||
## Example Workflows
|
||||
|
||||
### Setting and retrieving values
|
||||
```javascript
|
||||
// Set a measurement
|
||||
container.type('flow')
|
||||
.variant('measured')
|
||||
.position('upstream')
|
||||
.value(120)
|
||||
.unit('gpm');
|
||||
|
||||
// Retrieve the same measurement
|
||||
const flow = container.type('flow')
|
||||
.variant('measured')
|
||||
.position('upstream')
|
||||
.getCurrentValue();
|
||||
```
|
||||
|
||||
### Calculating differences
|
||||
```javascript
|
||||
// Set upstream and downstream measurements
|
||||
container.type('pressure').variant('measured').position('upstream').value(100).unit('psi');
|
||||
container.type('pressure').variant('measured').position('downstream').value(95).unit('psi');
|
||||
|
||||
// Calculate the difference
|
||||
const diff = container.type('pressure').variant('measured').difference();
|
||||
console.log(`Pressure drop: ${diff.currentDiff} ${diff.unit}`);
|
||||
```
|
||||
|
||||
### Working with historical data
|
||||
```javascript
|
||||
// Add multiple values
|
||||
container.type('temperature')
|
||||
.variant('measured')
|
||||
.position('outlet')
|
||||
.value(72)
|
||||
.value(74)
|
||||
.value(73)
|
||||
.unit('F');
|
||||
|
||||
// Get statistics
|
||||
const avg = container.type('temperature').variant('measured').position('outlet').getAverage();
|
||||
const min = container.type('temperature').variant('measured').position('outlet').getMin();
|
||||
const max = container.type('temperature').variant('measured').position('outlet').getMax();
|
||||
```
|
||||
58
src/measurements/examples.js
Normal file
58
src/measurements/examples.js
Normal file
@@ -0,0 +1,58 @@
|
||||
const { MeasurementContainer } = require('./index');
|
||||
|
||||
// Create a container
|
||||
const container = new MeasurementContainer({ windowSize: 20 });
|
||||
|
||||
// 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);
|
||||
|
||||
// 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;
|
||||
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;
|
||||
console.log(`Downstream pressure: ${downstreamValue} ${downstreamUnit}`);
|
||||
|
||||
// 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');
|
||||
|
||||
const flowAvg = container.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();
|
||||
console.log(`Pressure difference: ${pressureDiff.value} ${pressureDiff.unit}`);
|
||||
|
||||
// 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');
|
||||
|
||||
// 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())}`);
|
||||
|
||||
// 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());
|
||||
|
||||
module.exports = {
|
||||
runExamples: () => {
|
||||
console.log('Examples of the measurement chainable API');
|
||||
}
|
||||
};
|
||||
9
src/measurements/index.js
Normal file
9
src/measurements/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const MeasurementContainer = require('./MeasurementContainer');
|
||||
const Measurement = require('./Measurement');
|
||||
const MeasurementBuilder = require('./MeasurementBuilder');
|
||||
|
||||
module.exports = {
|
||||
MeasurementContainer,
|
||||
Measurement,
|
||||
MeasurementBuilder
|
||||
};
|
||||
Reference in New Issue
Block a user