'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const DriftAssessor = require('../../src/drift/driftAssessor'); /* ---- fakes ---- */ function fakeMeasurements(predictedValue) { return { type() { return this; }, variant() { return this; }, position() { return this; }, getCurrentValue() { return predictedValue; }, getAllValues() { return { values: [predictedValue], timestamps: [1] }; }, }; } function makeErrorMetrics(driftFactory) { return { assessPoint: (metricId, predicted, measured, opts) => driftFactory(metricId, predicted, measured, opts), assessDrift: () => ({ nrmse: 0.1, valid: true }), }; } const SILENT = { warn() {}, debug() {} }; test('updateMetricDrift returns drift object when predicted+measured both finite', () => { const drift = { valid: true, nrmse: 0.05, immediateLevel: 0, longTermLevel: 0 }; const assessor = new DriftAssessor({ errorMetrics: makeErrorMetrics(() => drift), measurements: fakeMeasurements(10), driftProfiles: { flow: {} }, logger: SILENT, }); const out = assessor.updateMetricDrift('flow', 11); assert.deepEqual(out, drift); assert.equal(assessor.latest.flow, drift); }); test('updateMetricDrift returns null when predicted is non-finite', () => { const assessor = new DriftAssessor({ errorMetrics: makeErrorMetrics(() => ({ valid: true })), measurements: fakeMeasurements(NaN), driftProfiles: {}, logger: SILENT, }); assert.equal(assessor.updateMetricDrift('flow', 5), null); }); test('updateMetricDrift catches errorMetrics throw and logs', () => { const warns = []; const assessor = new DriftAssessor({ errorMetrics: { assessPoint() { throw new Error('boom'); } }, measurements: fakeMeasurements(10), driftProfiles: {}, logger: { warn(m) { warns.push(m); }, debug() {} }, }); const out = assessor.updateMetricDrift('flow', 11); assert.equal(out, null); assert.match(warns[0], /Drift update failed for metric 'flow'/); }); test('applyDriftPenalty leaves confidence unchanged for null/invalid drift', () => { const assessor = new DriftAssessor({ logger: SILENT }); const flags = []; assert.equal(assessor.applyDriftPenalty(null, 0.9, flags, 'flow'), 0.9); assert.equal(assessor.applyDriftPenalty({ valid: false }, 0.9, flags, 'flow'), 0.9); assert.deepEqual(flags, []); }); test('applyDriftPenalty level 1 reduces confidence by 0.1 + flag', () => { const assessor = new DriftAssessor({ logger: SILENT }); const flags = []; const c = assessor.applyDriftPenalty( { valid: true, nrmse: 0.1, immediateLevel: 1, longTermLevel: 0 }, 0.9, flags, 'flow', ); assert.ok(Math.abs(c - 0.8) < 1e-9); assert.deepEqual(flags, ['flow_low_immediate_drift']); }); test('applyDriftPenalty level 2 reduces confidence by 0.2 + flag', () => { const assessor = new DriftAssessor({ logger: SILENT }); const flags = []; const c = assessor.applyDriftPenalty( { valid: true, nrmse: 0.2, immediateLevel: 2, longTermLevel: 0 }, 0.9, flags, 'power', ); assert.ok(Math.abs(c - 0.7) < 1e-9); assert.deepEqual(flags, ['power_medium_immediate_drift']); }); test('applyDriftPenalty level 3 reduces confidence by 0.3 + flag', () => { const assessor = new DriftAssessor({ logger: SILENT }); const flags = []; const c = assessor.applyDriftPenalty( { valid: true, nrmse: 0.5, immediateLevel: 3, longTermLevel: 0 }, 0.9, flags, 'flow', ); assert.ok(Math.abs(c - 0.6) < 1e-9); assert.deepEqual(flags, ['flow_high_immediate_drift']); }); test('applyDriftPenalty stacks long-term penalty', () => { const assessor = new DriftAssessor({ logger: SILENT }); const flags = []; const c = assessor.applyDriftPenalty( { valid: true, nrmse: 0.4, immediateLevel: 2, longTermLevel: 2 }, 0.9, flags, 'flow', ); assert.ok(Math.abs(c - 0.6) < 1e-9); assert.deepEqual(flags, ['flow_medium_immediate_drift', 'flow_long_term_drift']); }); test('assessDrift returns null if no stored series', () => { const assessor = new DriftAssessor({ errorMetrics: makeErrorMetrics(() => ({ valid: true })), measurements: { type() { return this; }, variant() { return this; }, position() { return this; }, getAllValues() { return {}; }, }, driftProfiles: {}, logger: SILENT, }); assert.equal(assessor.assessDrift('flow', 0, 1), null); });