diff --git a/src/specificClass.js b/src/specificClass.js index 707b66c..d1fe4bb 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -676,35 +676,40 @@ _callMeasurementHandler(measurementType, value, position, context) { } _registerFailure(cause, runtimeOverride) { - const runtime = runtimeOverride ?? this.state.getRunTimeHours(); - const maintenance_time = this.state.getMaintenanceTimeHours(); + const runtime = runtimeOverride ?? this.state.getRunTimeHours(); + const maintenanceTime = this.state.getMaintenanceTimeHours(); - this.kpi.failures += 1; - this.kpi.rulFailureTriggered = false; - this.kpi.lastFailureTime = Date.now(); - this.kpi.lastFailureRuntime = runtime; - this.kpi.lastFailureCause = cause; + this.kpi.failures += 1; + this.kpi.lastFailureTime = Date.now(); + this.kpi.lastFailureRuntime = runtime; + this.kpi.lastFailureCause = cause; - const failures = this.kpi.failures; + this.kpi.cycleMTBF = + this.kpi.failures > 0 + ? runtime / this.kpi.failures + : runtime; - // If no failures yet: MTBF = total runtime & MTTR = 0 - this.kpi.MTBF = failures > 0 ? runtime / failures : runtime; - this.kpi.MTTR = failures > 0 ? maintenance_time / failures : 0; + this.kpi.lastRUL = null; + this.kpi.rulFailureTriggered = false; - const mtbf = this.kpi.MTBF ?? 0; - const mttr = this.kpi.MTTR ?? 0; + this.kpi.MTTR = + this.kpi.failures > 0 + ? maintenanceTime / this.kpi.failures + : 0; - if (mtbf <= 0 && mttr <= 0) { - this.kpi.availability = 1; - } else { - const availability = mtbf / (mtbf + mttr); - this.kpi.availability = Math.min(1, Math.max(0, availability)); - } + const mtbf = this.kpi.cycleMTBF ?? 0; + const mttr = this.kpi.MTTR ?? 0; - this.logger.warn( - `Failure registered (cause=${cause}). Total failures=${this.kpi.failures}, runtime=${runtime.toFixed(2)}h` + if (mtbf <= 0 && mttr <= 0) { + this.kpi.availability = 1; + } else { + this.kpi.availability = Math.min( + 1, + Math.max(0, mtbf / (mtbf + mttr)) ); } +} + _handleStateChangeForKPI(newState) { const runtime = this.state.getRunTimeHours(); @@ -839,65 +844,51 @@ _callMeasurementHandler(measurementType, value, position, context) { } -calculateSimpleRUL() { - const mtbf = this.kpi?.MTBF ?? 0; - const healthIndex = typeof this.assetHealth?.index === "number" - ? this.assetHealth.index - : 0; + calculateSimpleRUL() { + const mtbf = + this.kpi.cycleMTBF ?? + this.kpi.MTBF ?? + 0; - if (this.kpi?.rulFailureTriggered) { - this.kpi.lastRUL = 0; - return 0; - } + if (mtbf <= 0) { + this.kpi.lastRUL = 0; + return 0; + } - if (mtbf <= 0) { - this.logger.debug("No MTBF available, RUL cannot be estimated."); - this.kpi.lastRUL = 0; - return 0; - } + const runtime = this.state?.getRunTimeHours?.() ?? 0; + const lastFailureRuntime = this.kpi?.lastFailureRuntime ?? 0; + const age = Math.max(0, runtime - lastFailureRuntime); - const clampedHI = Math.min(5, Math.max(0, healthIndex)); - const runtime = this.state?.getRunTimeHours?.() ?? 0; - const lastFailureRuntime = this.kpi?.lastFailureRuntime ?? 0; - const ageSinceLastFailure = Math.max(0, runtime - lastFailureRuntime); + const baseFractionLeft = Math.max(0, 1 - age / mtbf); + let rul = mtbf * baseFractionLeft; - const fractionByHealth = (5 - clampedHI) / 5; - const fractionByAge = Math.max(0, 1 - ageSinceLastFailure / mtbf); - const fractionLeft = Math.min(fractionByHealth, fractionByAge); + const healthIndex = Math.min(5, Math.max(0, this.assetHealth?.index ?? 0)); - let rul = mtbf * fractionLeft; - if (rul < 0) rul = 0; + const decayFactor = 1 + (healthIndex / 5) * 2; + const acceleratedAge = age * decayFactor; + const acceleratedFractionLeft = Math.max(0, 1 - acceleratedAge / mtbf); + rul = mtbf * acceleratedFractionLeft; - const prevRUL = this.kpi.lastRUL ?? null; - const EPS = 1e-3; + if (this.kpi.lastRUL != null) { + rul = Math.min(this.kpi.lastRUL, rul); + } - const state = this.state?.getCurrentState?.(); - const isOperational = ["operational", "accelerating", "decelerating"].includes(state); + const EPS = 1e-3; + const state = this.state?.getCurrentState?.(); + const isOperational = ["operational", "accelerating", "decelerating"].includes(state); - if (isOperational) { - const hadPrev = prevRUL !== null; - const wasPositive = hadPrev && prevRUL > EPS; - const isNowZeroish = rul <= EPS; - const isDecreasing = hadPrev && rul <= prevRUL + EPS; - - if (wasPositive && isNowZeroish && isDecreasing) { + if (isOperational && rul <= EPS && !this.kpi.rulFailureTriggered) { this.kpi.rulFailureTriggered = true; - this.kpi.lastFailureTime = Date.now(); this.kpi.lastFailureRuntime = runtime; this.kpi.lastFailureCause = "RUL"; - rul = 0; } + + this.kpi.lastRUL = rul; + return rul; } - this.kpi.lastRUL = rul; - - return rul; -} - - - updateCurve(newCurve) { this.logger.info(`Updating machine curve`); const newConfig = { asset: { machineCurve: newCurve } }; @@ -968,9 +959,10 @@ calculateSimpleRUL() { output["asset_tag_number"] = 'L001'; // hardcoded for now output["maintenanceMode"] = this.kpi.maintenanceMode; output["maintenanceTime"] = this.state.getMaintenanceTimeHours(); - output["rul_hours"] = this.calculateSimpleRUL(); + this._calculateAssetHealthIndex(); output["assetHealthIndex"] = this.assetHealth.index; + output["rul_hours"] = this.calculateSimpleRUL(); const healthColors = ["#006400", "#008000", "#FFFF00", "#FFA500", "#FF0000", "#8B0000"]; // 0 = darkgreen, 1 = green, 2 = yellow, 3 = orange, 4 = red, 5 = darkred output["assetHealthColor"] = healthColors[this.assetHealth.index] || "unknown";