Complete general functions
This commit is contained in:
297
helper/nrmse/errorMetric.test.js
Normal file
297
helper/nrmse/errorMetric.test.js
Normal file
@@ -0,0 +1,297 @@
|
||||
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);
|
||||
154
helper/nrmse/errorMetrics.js
Normal file
154
helper/nrmse/errorMetrics.js
Normal file
@@ -0,0 +1,154 @@
|
||||
//load local dependencies
|
||||
const EventEmitter = require('events');
|
||||
|
||||
//load all config modules
|
||||
const defaultConfig = require('./nrmseConfig.json');
|
||||
const ConfigUtils = require('../configUtils');
|
||||
|
||||
class ErrorMetrics {
|
||||
constructor(config = {}, logger) {
|
||||
|
||||
this.emitter = new EventEmitter(); // Own EventEmitter
|
||||
this.configUtils = new ConfigUtils(defaultConfig);
|
||||
this.config = this.configUtils.initConfig(config);
|
||||
|
||||
// Init after config is set
|
||||
this.logger = logger;
|
||||
|
||||
// For long-term NRMSD accumulation
|
||||
this.cumNRMSD = 0;
|
||||
this.cumCount = 0;
|
||||
}
|
||||
|
||||
//INCLUDE timestamps in the next update OLIFANT
|
||||
meanSquaredError(predicted, measured) {
|
||||
if (predicted.length !== measured.length) {
|
||||
this.logger.error("Comparing MSE Arrays must have the same length.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
let sumSqError = 0;
|
||||
for (let i = 0; i < predicted.length; i++) {
|
||||
const err = predicted[i] - measured[i];
|
||||
sumSqError += err * err;
|
||||
}
|
||||
return sumSqError / predicted.length;
|
||||
}
|
||||
|
||||
rootMeanSquaredError(predicted, measured) {
|
||||
return Math.sqrt(this.meanSquaredError(predicted, measured));
|
||||
}
|
||||
|
||||
normalizedRootMeanSquaredError(predicted, measured, processMin, processMax) {
|
||||
const range = processMax - processMin;
|
||||
if (range <= 0) {
|
||||
this.logger.error("Invalid process range: processMax must be greater than processMin.");
|
||||
}
|
||||
const rmse = this.rootMeanSquaredError(predicted, measured);
|
||||
return rmse / range;
|
||||
}
|
||||
|
||||
longTermNRMSD(input) {
|
||||
|
||||
const storedNRMSD = this.cumNRMSD;
|
||||
const storedCount = this.cumCount;
|
||||
const newCount = storedCount + 1;
|
||||
|
||||
// Update cumulative values
|
||||
this.cumCount = newCount;
|
||||
|
||||
// Calculate new running average
|
||||
if (storedCount === 0) {
|
||||
this.cumNRMSD = input; // First value
|
||||
} else {
|
||||
// Running average formula: newAvg = oldAvg + (newValue - oldAvg) / newCount
|
||||
this.cumNRMSD = storedNRMSD + (input - storedNRMSD) / newCount;
|
||||
}
|
||||
|
||||
if(newCount >= 100) {
|
||||
// Return the current NRMSD value, not just the contribution from this sample
|
||||
return this.cumNRMSD;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
normalizeUsingRealtime(predicted, measured) {
|
||||
const realtimeMin = Math.min(Math.min(...predicted), Math.min(...measured));
|
||||
const realtimeMax = Math.max(Math.max(...predicted), Math.max(...measured));
|
||||
const range = realtimeMax - realtimeMin;
|
||||
if (range <= 0) {
|
||||
throw new Error("Invalid process range: processMax must be greater than processMin.");
|
||||
}
|
||||
const rmse = this.rootMeanSquaredError(predicted, measured);
|
||||
return rmse / range;
|
||||
}
|
||||
|
||||
detectImmediateDrift(nrmse) {
|
||||
let ImmDrift = {};
|
||||
this.logger.debug(`checking immediate drift with thresholds : ${this.config.thresholds.NRMSE_HIGH} ${this.config.thresholds.NRMSE_MEDIUM} ${this.config.thresholds.NRMSE_LOW}`);
|
||||
switch (true) {
|
||||
case( nrmse > this.config.thresholds.NRMSE_HIGH ) :
|
||||
ImmDrift = {level : 3 , feedback : "High immediate drift detected"};
|
||||
break;
|
||||
case( nrmse > this.config.thresholds.NRMSE_MEDIUM ) :
|
||||
ImmDrift = {level : 2 , feedback : "Medium immediate drift detected"};
|
||||
break;
|
||||
case(nrmse > this.config.thresholds.NRMSE_LOW ):
|
||||
ImmDrift = {level : 1 , feedback : "Low immediate drift detected"};
|
||||
break;
|
||||
default:
|
||||
ImmDrift = {level : 0 , feedback : "No drift detected"};
|
||||
}
|
||||
return ImmDrift;
|
||||
}
|
||||
|
||||
detectLongTermDrift(longTermNRMSD) {
|
||||
let LongTermDrift = {};
|
||||
this.logger.debug(`checking longterm drift with thresholds : ${this.config.thresholds.LONG_TERM_HIGH} ${this.config.thresholds.LONG_TERM_MEDIUM} ${this.config.thresholds.LONG_TERM_LOW}`);
|
||||
switch (true) {
|
||||
case(Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_HIGH) :
|
||||
LongTermDrift = {level : 3 , feedback : "High long-term drift detected"};
|
||||
break;
|
||||
case (Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_MEDIUM) :
|
||||
LongTermDrift = {level : 2 , feedback : "Medium long-term drift detected"};
|
||||
break;
|
||||
case ( Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_LOW ) :
|
||||
LongTermDrift = {level : 1 , feedback : "Low long-term drift detected"};
|
||||
break;
|
||||
default:
|
||||
LongTermDrift = {level : 0 , feedback : "No drift detected"};
|
||||
}
|
||||
return LongTermDrift;
|
||||
}
|
||||
|
||||
detectDrift(nrmse, longTermNRMSD) {
|
||||
const ImmDrift = this.detectImmediateDrift(nrmse);
|
||||
const LongTermDrift = this.detectLongTermDrift(longTermNRMSD);
|
||||
return { ImmDrift, LongTermDrift };
|
||||
}
|
||||
|
||||
// asses the drift
|
||||
assessDrift(predicted, measured, processMin, processMax) {
|
||||
// Compute NRMSE and check for immediate drift
|
||||
const nrmse = this.normalizedRootMeanSquaredError(predicted, measured, processMin, processMax);
|
||||
this.logger.debug(`NRMSE: ${nrmse}`);
|
||||
// cmopute long-term NRMSD and add result to cumalitve NRMSD
|
||||
const longTermNRMSD = this.longTermNRMSD(nrmse);
|
||||
// return the drift
|
||||
// Return the drift assessment object
|
||||
const driftAssessment = this.detectDrift(nrmse, longTermNRMSD);
|
||||
return {
|
||||
nrmse,
|
||||
longTermNRMSD,
|
||||
immediateLevel: driftAssessment.ImmDrift.level,
|
||||
immediateFeedback: driftAssessment.ImmDrift.feedback,
|
||||
longTermLevel: driftAssessment.LongTermDrift.level,
|
||||
longTermFeedback: driftAssessment.LongTermDrift.feedback
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
module.exports = ErrorMetrics;
|
||||
138
helper/nrmse/nrmseConfig.json
Normal file
138
helper/nrmse/nrmseConfig.json
Normal file
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"general": {
|
||||
"name": {
|
||||
"default": "ErrorMetrics",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "A human-readable name for the configuration."
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"default": null,
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "A unique identifier for this configuration, assigned dynamically when needed."
|
||||
}
|
||||
},
|
||||
"unit": {
|
||||
"default": "unitless",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "The unit used for the state values (e.g., 'meters', 'seconds', 'unitless')."
|
||||
}
|
||||
},
|
||||
"logging": {
|
||||
"logLevel": {
|
||||
"default": "info",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "debug",
|
||||
"description": "Log messages are printed for debugging purposes."
|
||||
},
|
||||
{
|
||||
"value": "info",
|
||||
"description": "Informational messages are printed."
|
||||
},
|
||||
{
|
||||
"value": "warn",
|
||||
"description": "Warning messages are printed."
|
||||
},
|
||||
{
|
||||
"value": "error",
|
||||
"description": "Error messages are printed."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"enabled": {
|
||||
"default": true,
|
||||
"rules": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates whether logging is active. If true, log messages will be generated."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"functionality": {
|
||||
"softwareType": {
|
||||
"default": "errorMetrics",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "Logical name identifying the software type."
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"default": "error calculation",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "Functional role within the system."
|
||||
}
|
||||
}
|
||||
},
|
||||
"mode": {
|
||||
"current": {
|
||||
"default": "active",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "active",
|
||||
"description": "The error metrics calculation is active."
|
||||
},
|
||||
{
|
||||
"value": "inactive",
|
||||
"description": "The error metrics calculation is inactive."
|
||||
}
|
||||
],
|
||||
"description": "The operational mode of the error metrics calculation."
|
||||
}
|
||||
}
|
||||
},
|
||||
"thresholds": {
|
||||
"NRMSE_LOW": {
|
||||
"default": 0.05,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "Low threshold for normalized root mean squared error."
|
||||
}
|
||||
},
|
||||
"NRMSE_MEDIUM": {
|
||||
"default": 0.10,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "Medium threshold for normalized root mean squared error."
|
||||
}
|
||||
},
|
||||
"NRMSE_HIGH": {
|
||||
"default": 0.15,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "High threshold for normalized root mean squared error."
|
||||
}
|
||||
},
|
||||
"LONG_TERM_LOW": {
|
||||
"default": 0.02,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "Low threshold for long-term normalized root mean squared deviation."
|
||||
}
|
||||
},
|
||||
"LONG_TERM_MEDIUM": {
|
||||
"default": 0.04,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "Medium threshold for long-term normalized root mean squared deviation."
|
||||
}
|
||||
},
|
||||
"LONG_TERM_HIGH": {
|
||||
"default": 0.06,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "High threshold for long-term normalized root mean squared deviation."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user