src/simulation/simulator.js random-walk generator (was simulateInput inline)
src/calibration/calibrator.js calibrate + isStable + evaluateRepeatability,
using generalFunctions/stats. NB: isStable
tautology preserved verbatim — see
OPEN_QUESTIONS.md 2026-05-10 for the bug.
src/commands/ registry + handlers (canonical names from start)
CONTRACT.md inputs/outputs/events surface
77 basic tests pass (62 pre-refactor + 15 new across the three new files).
specificClass.js / nodeClass.js untouched — integration is P3 wave 2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
92 lines
3.5 KiB
JavaScript
92 lines
3.5 KiB
JavaScript
'use strict';
|
|
|
|
const { stats } = require('generalFunctions');
|
|
|
|
const MARGIN_FACTOR = 2;
|
|
|
|
/**
|
|
* Calibration helper extracted from measurement/specificClass.js.
|
|
*
|
|
* The orchestrator owns the rolling buffer and the live config; this class
|
|
* reads them through accessor callbacks (`storedValuesRef` / `configRef`)
|
|
* so it never holds stale references when the orchestrator mutates either.
|
|
*/
|
|
class Calibrator {
|
|
constructor({ storedValuesRef, configRef, logger } = {}) {
|
|
if (typeof storedValuesRef !== 'function' || typeof configRef !== 'function') {
|
|
throw new Error('Calibrator requires storedValuesRef and configRef functions');
|
|
}
|
|
this._storedValues = storedValuesRef;
|
|
this._config = configRef;
|
|
this.logger = logger || { info() {}, warn() {}, debug() {}, error() {} };
|
|
}
|
|
|
|
/**
|
|
* Decide whether the rolling window is stable enough to trust.
|
|
* Mirrors the original threshold check; with `stdDev=0` (constant input)
|
|
* the comparison short-circuits to true.
|
|
*/
|
|
isStable() {
|
|
const values = this._storedValues();
|
|
if (!Array.isArray(values) || values.length < 2) {
|
|
return { isStable: false, stdDev: 0 };
|
|
}
|
|
const stdDev = stats.stdDev(values);
|
|
const stableThreshold = stdDev * MARGIN_FACTOR;
|
|
return { isStable: stdDev < stableThreshold || stdDev === 0, stdDev };
|
|
}
|
|
|
|
/**
|
|
* Compute the offset that drives `currentOutputAbs` to the configured
|
|
* baseline (scaling input-min when scaling is enabled, abs-min otherwise).
|
|
* Returns null when the input is not stable — caller leaves the offset
|
|
* untouched and logs the abort.
|
|
*/
|
|
calibrate(currentOutputAbs) {
|
|
const { isStable } = this.isStable();
|
|
if (!isStable) {
|
|
this.logger.warn('Large fluctuations detected between stored values. Calibration aborted.');
|
|
return null;
|
|
}
|
|
const cfg = this._config();
|
|
const scaling = (cfg && cfg.scaling) || {};
|
|
const baseline = scaling.enabled ? scaling.inputMin : scaling.absMin;
|
|
if (typeof baseline !== 'number' || !Number.isFinite(baseline)) {
|
|
this.logger.warn('Calibration baseline missing from config.scaling. Aborted.');
|
|
return null;
|
|
}
|
|
const offset = baseline - currentOutputAbs;
|
|
this.logger.info(`Stable input value detected. Calibration completed. Offset=${offset}`);
|
|
return { offset };
|
|
}
|
|
|
|
/**
|
|
* Repeatability proxy: the std-dev of the smoothed rolling buffer once
|
|
* stability is confirmed. Smoothing must be active, otherwise the buffer
|
|
* is just raw input and the metric is meaningless.
|
|
*/
|
|
evaluateRepeatability() {
|
|
const cfg = this._config();
|
|
const method = cfg && cfg.smoothing && cfg.smoothing.smoothMethod;
|
|
const normalized = typeof method === 'string' ? method.toLowerCase() : method;
|
|
if (normalized === 'none' || normalized == null) {
|
|
this.logger.warn('Repeatability evaluation is not possible without smoothing.');
|
|
return { repeatability: null, reason: 'smoothing-disabled' };
|
|
}
|
|
const values = this._storedValues();
|
|
if (!Array.isArray(values) || values.length < 2) {
|
|
this.logger.warn('Not enough data to evaluate repeatability.');
|
|
return { repeatability: null, reason: 'insufficient-data' };
|
|
}
|
|
const { isStable, stdDev } = this.isStable();
|
|
if (!isStable) {
|
|
this.logger.warn('Data not stable enough to evaluate repeatability.');
|
|
return { repeatability: null, reason: 'unstable' };
|
|
}
|
|
this.logger.info(`Repeatability evaluated. Standard Deviation: ${stdDev}`);
|
|
return { repeatability: stdDev };
|
|
}
|
|
}
|
|
|
|
module.exports = Calibrator;
|