diff --git a/src/configs/rotatingMachine.json b/src/configs/rotatingMachine.json index 102a0b3..a2e4a5e 100644 --- a/src/configs/rotatingMachine.json +++ b/src/configs/rotatingMachine.json @@ -16,7 +16,7 @@ } }, "unit": { - "default": "m3/h", + "default": "m3/s", "rules": { "type": "string", "description": "The default measurement unit for this configuration (e.g., 'meters', 'seconds', 'unitless')." diff --git a/src/measurements/MeasurementContainer.js b/src/measurements/MeasurementContainer.js index 68f7ed9..185fcb8 100644 --- a/src/measurements/MeasurementContainer.js +++ b/src/measurements/MeasurementContainer.js @@ -9,6 +9,7 @@ class MeasurementContainer { this.windowSize = options.windowSize || 10; // Default window size // For chaining context + this._currentChildId = null; this._currentType = null; this._currentVariant = null; this._currentPosition = null; @@ -49,6 +50,11 @@ class MeasurementContainer { return this; } + child(childId) { + this._currentChildId = childId || 'default'; + return this; + } + setChildName(childName) { this.childName = childName; return this; @@ -72,6 +78,13 @@ class MeasurementContainer { null; } + getUnit(type) { + if (!type) return null; + if (this.preferredUnits && this.preferredUnits[type]) return this.preferredUnits[type]; + if (this.defaultUnits && this.defaultUnits[type]) return this.defaultUnits[type]; + return null; + } + // Chainable methods type(typeName) { this._currentType = typeName; @@ -224,36 +237,47 @@ class MeasurementContainer { } // Terminal operations - get data out - get() { - if (!this._ensureChainIsValid()) return null; - return this._getOrCreateMeasurement(); - } + get() { + if (!this._ensureChainIsValid()) return null; + const variantBucket = this.measurements[this._currentType]?.[this._currentVariant]; + if (!variantBucket) return null; + const posBucket = variantBucket[this._currentPosition]; + if (!posBucket) return null; + + // Legacy single measurement + if (posBucket?.getCurrentValue) return posBucket; + + // Child-aware: pick requested child, otherwise fall back to default, otherwise first available + if (posBucket && typeof posBucket === 'object') { + const requestedKey = this._currentChildId || this.childId; + const keys = Object.keys(posBucket); + if (!keys.length) return null; + const measurement = + (requestedKey && posBucket[requestedKey]) || + posBucket.default || + posBucket[keys[0]]; + return measurement || null; + } + + return null; + } + + getCurrentValue(requestedUnit = null) { const measurement = this.get(); if (!measurement) return null; - const value = measurement.getCurrentValue(); if (value === null) return null; - - // Return as-is if no unit conversion requested - if (!requestedUnit) { + if (!requestedUnit || !measurement.unit || requestedUnit === measurement.unit) { 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 - } + 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 value; } getAverage(requestedUnit = null) { @@ -357,38 +381,85 @@ class MeasurementContainer { return sample; } + sum(type, variant, positions = [], targetUnit = null) { + const bucket = this.measurements?.[type]?.[variant]; + if (!bucket) return 0; + return positions + .map((pos) => { + const posBucket = bucket[pos]; + if (!posBucket) return 0; + return Object.values(posBucket) + .map((m) => { + if (!m?.getCurrentValue) return 0; + const val = m.getCurrentValue(); + if (val == null) return 0; + const fromUnit = m.unit || targetUnit; + if (!targetUnit || !fromUnit || fromUnit === targetUnit) return val; + try { return convertModule(val).from(fromUnit).to(targetUnit); } catch { return val; } + }) + .reduce((acc, v) => acc + (Number.isFinite(v) ? v : 0), 0); + }) + .reduce((acc, v) => acc + v, 0); + } + + getFlattenedOutput() { + const out = {}; + Object.entries(this.measurements).forEach(([type, variants]) => { + Object.entries(variants).forEach(([variant, positions]) => { + Object.entries(positions).forEach(([position, entry]) => { + // Legacy single series + if (entry?.getCurrentValue) { + out[`${type}.${variant}.${position}`] = entry.getCurrentValue(); + return; + } + // Child-bucketed series + if (entry && typeof entry === 'object') { + Object.entries(entry).forEach(([childId, m]) => { + if (m?.getCurrentValue) { + out[`${type}.${variant}.${position}.${childId}`] = m.getCurrentValue(); + } + }); + } + }); + }); + }); + return out; + } // Difference calculations between positions -difference({ from = "downstream", to = "upstream", unit: requestedUnit } = {}) { - if (!this._currentType || !this._currentVariant) { - throw new Error("Type and variant must be specified for difference calculation"); + difference({ from = "downstream", to = "upstream", unit: requestedUnit } = {}) { + if (!this._currentType || !this._currentVariant) { + throw new Error("Type and variant must be specified for difference calculation"); + } + + const get = pos => { + const bucket = this.measurements?.[this._currentType]?.[this._currentVariant]?.[pos]; + if (!bucket) return null; + // child-aware bucket: pick current childId/default or first available + if (bucket && typeof bucket === 'object' && !bucket.getCurrentValue) { + const childKey = this._currentChildId || this.childId || Object.keys(bucket)[0]; + return bucket?.[childKey] || null; + } + // legacy single measurement + return bucket; + }; + + const a = get(from); + const b = get(to); + if (!a || !b || !a.values || !b.values || a.values.length === 0 || b.values.length === 0) { + return null; + } + + const targetUnit = requestedUnit || a.unit || b.unit; + const aVal = this._convertValueToUnit(a.getCurrentValue(), a.unit, targetUnit); + const bVal = this._convertValueToUnit(b.getCurrentValue(), b.unit, targetUnit); + + const aAvg = this._convertValueToUnit(a.getAverage(), a.unit, targetUnit); + const bAvg = this._convertValueToUnit(b.getAverage(), b.unit, targetUnit); + + return { value: aVal - bVal, avgDiff: aAvg - bAvg, unit: targetUnit, from, to }; } - const get = pos => - this.measurements?.[this._currentType]?.[this._currentVariant]?.[pos] || null; - - const a = get(from); - const b = get(to); - if (!a || !b || a.values.length === 0 || b.values.length === 0) { - return null; - } - - const targetUnit = requestedUnit || a.unit || b.unit; - const aVal = this._convertValueToUnit(a.getCurrentValue(), a.unit, targetUnit); - const bVal = this._convertValueToUnit(b.getCurrentValue(), b.unit, targetUnit); - - const aAvg = this._convertValueToUnit(a.getAverage(), a.unit, targetUnit); - const bAvg = this._convertValueToUnit(b.getAverage(), b.unit, targetUnit); - - return { - value: aVal - bVal, - avgDiff: aAvg - bAvg, - unit: targetUnit, - from, - to, - }; -} - // Helper methods _ensureChainIsValid() { if (!this._currentType || !this._currentVariant || !this._currentPosition) { @@ -410,18 +481,26 @@ difference({ from = "downstream", to = "upstream", unit: requestedUnit } = {}) { 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) - .setDistance(this._currentDistance) - .build(); + const positionKey = this._currentPosition; + const childKey = this._currentChildId || this.childId || 'default'; + + if (!this.measurements[this._currentType][this._currentVariant][positionKey]) { + this.measurements[this._currentType][this._currentVariant][positionKey] = {}; } - - return this.measurements[this._currentType][this._currentVariant][this._currentPosition]; + + const bucket = this.measurements[this._currentType][this._currentVariant][positionKey]; + + if (!bucket[childKey]) { + bucket[childKey] = new MeasurementBuilder() + .setType(this._currentType) + .setVariant(this._currentVariant) + .setPosition(positionKey) + .setWindowSize(this.windowSize) + .setDistance(this._currentDistance) + .build(); + } + + return bucket[childKey]; } // Additional utility methods diff --git a/src/measurements/examples.js b/src/measurements/examples.js index 5cc2459..4e513c4 100644 --- a/src/measurements/examples.js +++ b/src/measurements/examples.js @@ -177,18 +177,18 @@ const downstreamData = basicContainer .get(); //check wether a serie exists -const hasSeries = measurements +const hasSeries = basicContainer .type("flow") .variant("measured") .exists(); // true if any position exists -const hasUpstreamValues = measurements +const hasUpstreamValues = basicContainer .type("flow") .variant("measured") .exists({ position: "upstream", requireValues: true }); // Passing everything explicitly -const hasPercent = measurements.exists({ +const hasPercent = basicContainer.exists({ type: "volume", variant: "percent", position: "atEquipment", @@ -274,14 +274,14 @@ console.log(` History: [${allValues.values.join(', ')}]\n`); console.log('--- Lagged sample comparison ---'); -const latest = stats.getCurrentValue(); // existing helper -const prevSample = stats.getLaggedValue(1); // new helper -const prevPrevSample = stats.getLaggedValue(2); // optional +const latestSample = stats.getLaggedSample(0); // newest sample object +const prevSample = stats.getLaggedSample(1); +const prevPrevSample = stats.getLaggedSample(2); if (prevSample) { - const delta = latest - prevSample.value; + const delta = (latestSample?.value ?? 0) - (prevSample.value ?? 0); console.log( - `Current vs previous: ${latest} ${statsData.unit} (t=${stats.get().getLatestTimestamp()}) vs ` + + `Current vs previous: ${latestSample?.value} ${statsData.unit} (t=${latestSample?.timestamp}) vs ` + `${prevSample.value} ${prevSample.unit} (t=${prevSample.timestamp})` ); console.log(`Δ = ${delta.toFixed(2)} ${statsData.unit}`);