Files
generalFunctions/test/basic/stats.basic.test.js
znetsixe 47faf94048 Phase 1 wave 1: domain + nodered + stats infra (additive)
Adds platform infrastructure used by the upcoming refactor of
nodeClass / specificClass across all 12 nodes:

- src/domain/UnitPolicy.js     — extracted from rotatingMachine/MGC
- src/domain/ChildRouter.js    — declarative event routing on top of childRegistrationUtils
- src/domain/LatestWinsGate.js — extracted from MGC dispatch gate
- src/domain/HealthStatus.js   — standardised {level, flags, message, source}
- src/nodered/statusBadge.js   — compose / error / idle / byState / text helpers
- src/stats/index.js           — mean / stdDev / median / mad / lerp

All additive — no existing exports change shape.
56 unit tests pass under node:test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 18:27:29 +02:00

51 lines
1.5 KiB
JavaScript

'use strict';
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { mean, stdDev, median, mad, lerp } = require('../../src/stats');
const EPS = 1e-9;
function near(a, b, eps = EPS) {
assert.ok(Math.abs(a - b) <= eps, `expected ${a}${b} (eps ${eps})`);
}
test('mean: basic and empty', () => {
assert.equal(mean([1, 2, 3, 4]), 2.5);
assert.equal(mean([]), 0);
});
test('stdDev: zero-variance, classic sample, single-element, empty', () => {
assert.equal(stdDev([1, 1, 1, 1]), 0);
near(stdDev([1, 2, 3, 4, 5]), 1.5811388300841898);
assert.equal(stdDev([5]), 0);
assert.equal(stdDev([]), 0);
});
test('median: odd, even, empty', () => {
assert.equal(median([1, 2, 3, 4, 5]), 3);
assert.equal(median([1, 2, 3, 4]), 2.5);
assert.equal(median([]), 0);
});
test('mad: hand-checked sample and constant array', () => {
// [1,1,2,2,4,6,9] -> median 2 -> |dev| [1,1,0,0,2,4,7] -> sorted
// [0,0,1,1,2,4,7] -> mad = 1.
assert.equal(mad([1, 1, 2, 2, 4, 6, 9]), 1);
assert.equal(mad([5, 5, 5]), 0);
assert.equal(mad([]), 0);
});
test('lerp: in-range mapping and degenerate pass-through', () => {
assert.equal(lerp(2, 0, 4, 0, 100), 50);
assert.equal(lerp(2, 0, 0, 0, 100), 2);
// iMin > iMax also degenerate (defensive against swapped bounds).
assert.equal(lerp(2, 4, 0, 0, 100), 2);
});
test('lerp: float arithmetic stays within epsilon', () => {
near(lerp(0.1, 0, 1, 0, 10), 1);
near(lerp(1 / 3, 0, 1, 0, 30), 10);
});