const ErrorMetrics = require('./errorMetrics'); // Dummy logger for tests const logger = { error: console.error, debug: console.log, info: console.log }; const config = { thresholds: { NRMSE_LOW: 0.05, NRMSE_MEDIUM: 0.10, NRMSE_HIGH: 0.15, LONG_TERM_LOW: 0.02, LONG_TERM_MEDIUM: 0.04, LONG_TERM_HIGH: 0.06 } }; class ErrorMetricsTester { constructor() { this.totalTests = 0; this.passedTests = 0; this.failedTests = 0; this.errorMetrics = new ErrorMetrics(config, logger); } assert(condition, message) { this.totalTests++; if (condition) { console.log(`✓ PASS: ${message}`); this.passedTests++; } else { console.log(`✗ FAIL: ${message}`); this.failedTests++; } } testMeanSquaredError() { console.log("\nTesting Mean Squared Error..."); const predicted = [1, 2, 3]; const measured = [1, 3, 5]; const mse = this.errorMetrics.meanSquaredError(predicted, measured); this.assert(Math.abs(mse - 1.67) < 0.1, "MSE correctly calculated"); } testRootMeanSquaredError() { console.log("\nTesting Root Mean Squared Error..."); const predicted = [1, 2, 3]; const measured = [1, 3, 5]; const rmse = this.errorMetrics.rootMeanSquaredError(predicted, measured); this.assert(Math.abs(rmse - 1.29) < 0.1, "RMSE correctly calculated"); } testNormalizedRMSE() { console.log("\nTesting Normalized RMSE..."); const predicted = [100, 102, 104]; const measured = [98, 103, 107]; const processMin = 90, processMax = 110; const nrmse = this.errorMetrics.normalizedRootMeanSquaredError(predicted, measured, processMin, processMax); this.assert(typeof nrmse === 'number' && nrmse > 0, "Normalized RMSE calculated correctly"); } testNormalizeUsingRealtime() { console.log("\nTesting Normalize Using Realtime..."); const predicted = [100, 102, 104]; const measured = [98, 103, 107]; try { const nrmse = this.errorMetrics.normalizeUsingRealtime(predicted, measured); this.assert(typeof nrmse === 'number' && nrmse > 0, "Normalize using realtime calculated correctly"); } catch (error) { this.assert(false, `Normalize using realtime failed: ${error.message}`); } // Test with identical values to check error handling const sameValues = [100, 100, 100]; try { this.errorMetrics.normalizeUsingRealtime(sameValues, sameValues); this.assert(false, "Should throw error with identical values"); } catch (error) { this.assert(true, "Correctly throws error when min/max are the same"); } } testLongTermNRMSD() { console.log("\nTesting Long Term NRMSD Accumulation..."); // Reset the accumulation values this.errorMetrics.cumNRMSD = 0; this.errorMetrics.cumCount = 0; let lastValue = 0; for (let i = 0; i < 100; i++) { lastValue = this.errorMetrics.longTermNRMSD(0.1 + i * 0.001); } this.assert( this.errorMetrics.cumCount === 100 && this.errorMetrics.cumNRMSD !== 0 && lastValue !== 0, "Long term NRMSD accumulates over 100 iterations" ); // Test that values are returned only after accumulating 100 samples this.errorMetrics.cumNRMSD = 0; this.errorMetrics.cumCount = 0; for (let i = 0; i < 99; i++) { const result = this.errorMetrics.longTermNRMSD(0.1); this.assert(result === 0, "No longTermNRMSD returned before 100 samples"); } // Use a different value for the 100th sample to ensure a non-zero result const result = this.errorMetrics.longTermNRMSD(0.2); this.assert(result !== 0, "longTermNRMSD returned after 100 samples"); } testDetectImmediateDrift() { console.log("\nTesting Immediate Drift Detection..."); // Test high drift let drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_HIGH + 0.01); this.assert(drift.level === 3, "Detects high immediate drift correctly"); // Test medium drift drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_MEDIUM + 0.01); this.assert(drift.level === 2, "Detects medium immediate drift correctly"); // Test low drift drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_LOW + 0.01); this.assert(drift.level === 1, "Detects low immediate drift correctly"); // Test no drift drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_LOW - 0.01); this.assert(drift.level === 0, "Detects no immediate drift correctly"); } testDetectLongTermDrift() { console.log("\nTesting Long Term Drift Detection..."); // Test high drift let drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_HIGH + 0.01); this.assert(drift.level === 3, "Detects high long-term drift correctly"); // Test medium drift drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_MEDIUM + 0.01); this.assert(drift.level === 2, "Detects medium long-term drift correctly"); // Test low drift drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_LOW + 0.01); this.assert(drift.level === 1, "Detects low long-term drift correctly"); // Test no drift drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_LOW - 0.01); this.assert(drift.level === 0, "Detects no long-term drift correctly"); // Test negative drift values drift = this.errorMetrics.detectLongTermDrift(-config.thresholds.LONG_TERM_HIGH - 0.01); this.assert(drift.level === 3, "Detects negative high long-term drift correctly"); } testDriftDetection() { console.log("\nTesting Combined Drift Detection..."); let nrmseHigh = config.thresholds.NRMSE_HIGH + 0.01; let ltNRMSD = 0; let result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD); this.assert( result !== null && result.ImmDrift && result.ImmDrift.level === 3 && result.LongTermDrift.level === 0, "Detects high immediate drift with no long-term drift" ); nrmseHigh = config.thresholds.NRMSE_LOW - 0.01; ltNRMSD = config.thresholds.LONG_TERM_MEDIUM + 0.01; result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD); this.assert( result !== null && result.ImmDrift.level === 0 && result.LongTermDrift && result.LongTermDrift.level === 2, "Detects medium long-term drift with no immediate drift" ); nrmseHigh = config.thresholds.NRMSE_MEDIUM + 0.01; ltNRMSD = config.thresholds.LONG_TERM_MEDIUM + 0.01; result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD); this.assert( result.ImmDrift.level === 2 && result.LongTermDrift.level === 2, "Detects both medium immediate and medium long-term drift" ); nrmseHigh = config.thresholds.NRMSE_LOW - 0.01; ltNRMSD = config.thresholds.LONG_TERM_LOW - 0.01; result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD); this.assert( result.ImmDrift.level === 0 && result.LongTermDrift.level === 0, "No significant drift detected when under thresholds" ); } testAssessDrift() { console.log("\nTesting assessDrift function..."); // Reset accumulation for testing this.errorMetrics.cumNRMSD = 0; this.errorMetrics.cumCount = 0; const predicted = [100, 101, 102, 103]; const measured = [90, 91, 92, 93]; const processMin = 90, processMax = 110; let result = this.errorMetrics.assessDrift(predicted, measured, processMin, processMax); this.assert( result !== null && typeof result.nrmse === 'number' && typeof result.longTermNRMSD === 'number' && typeof result.immediateLevel === 'number' && typeof result.immediateFeedback === 'string' && typeof result.longTermLevel === 'number' && typeof result.longTermFeedback === 'string', "assessDrift returns complete result structure" ); this.assert( result.immediateLevel > 0, "assessDrift detects immediate drift with significant difference" ); // Test with identical values result = this.errorMetrics.assessDrift(predicted, predicted, processMin, processMax); this.assert( result.nrmse === 0 && result.immediateLevel === 0, "assessDrift indicates no immediate drift when predicted equals measured" ); // Test with slight drift const measuredSlight = [100, 100.5, 101, 101.5]; result = this.errorMetrics.assessDrift(predicted, measuredSlight, processMin, processMax); this.assert( result !== null && result.nrmse < 0.05 && (result.immediateLevel < 2), "assessDrift returns appropriate levels for slight drift" ); // Test long-term drift accumulation for (let i = 0; i < 100; i++) { this.errorMetrics.assessDrift( predicted, measured.map(m => m + (Math.random() * 2 - 1)), // Add small random fluctuation processMin, processMax ); } result = this.errorMetrics.assessDrift(predicted, measured, processMin, processMax); this.assert( result.longTermNRMSD !== 0, "Long-term drift accumulates over multiple assessments" ); } async runAllTests() { console.log("\nStarting Error Metrics Tests...\n"); this.testMeanSquaredError(); this.testRootMeanSquaredError(); this.testNormalizedRMSE(); this.testNormalizeUsingRealtime(); this.testLongTermNRMSD(); this.testDetectImmediateDrift(); this.testDetectLongTermDrift(); this.testDriftDetection(); this.testAssessDrift(); console.log("\nTest Summary:"); console.log(`Total Tests: ${this.totalTests}`); console.log(`Passed: ${this.passedTests}`); console.log(`Failed: ${this.failedTests}`); process.exit(this.failedTests > 0 ? 1 : 0); } } // Run all tests const tester = new ErrorMetricsTester(); tester.runAllTests().catch(console.error);