Delete the legacy _convertUnitValue helper on the domain and the duplicate convertUnitValue export on curveNormalizer; both were identical to UnitPolicy.convert. Callers in flowController, the curve normalizer, and buildQHCurve now go through this.unitPolicy. The contract in .claude/refactor/CONTRACTS.md §6 named these as the target migration; this finishes the rollout for rotatingMachine. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
82 lines
3.0 KiB
JavaScript
82 lines
3.0 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
|
|
const { UnitPolicy } = require('generalFunctions');
|
|
const {
|
|
normalizeMachineCurve,
|
|
normalizeCurveSection,
|
|
} = require('../../src/curves/curveNormalizer');
|
|
|
|
function makePolicy() {
|
|
return UnitPolicy.declare({
|
|
canonical: { pressure: 'Pa', flow: 'm3/s', power: 'W', temperature: 'K' },
|
|
output: { pressure: 'mbar', flow: 'm3/h', power: 'kW', temperature: 'C' },
|
|
curve: { pressure: 'mbar', flow: 'm3/h', power: 'kW', control: '%' },
|
|
});
|
|
}
|
|
|
|
function captureLogger() {
|
|
const warns = [];
|
|
return {
|
|
warn: (m) => warns.push(m),
|
|
warns,
|
|
};
|
|
}
|
|
|
|
test('normalizeMachineCurve: rejects raw without nq/np', () => {
|
|
const policy = makePolicy();
|
|
assert.throws(() => normalizeMachineCurve(null, policy), /missing required nq\/np/);
|
|
assert.throws(() => normalizeMachineCurve({ nq: { 700: { x: [0], y: [0] } } }, policy), /missing required nq\/np/);
|
|
assert.throws(() => normalizeMachineCurve({ np: { 700: { x: [0], y: [0] } } }, policy), /missing required nq\/np/);
|
|
});
|
|
|
|
test('normalizeMachineCurve: converts pressure mbar -> Pa and flow m3/h -> m3/s', () => {
|
|
const policy = makePolicy();
|
|
const raw = {
|
|
nq: {
|
|
1000: { x: [0, 100], y: [0, 3600] }, // 3600 m3/h = 1 m3/s
|
|
},
|
|
np: {
|
|
1000: { x: [0, 100], y: [0, 1] }, // 1 kW = 1000 W
|
|
},
|
|
};
|
|
const out = normalizeMachineCurve(raw, policy);
|
|
// 1000 mbar = 100000 Pa
|
|
const pressureKey = Object.keys(out.nq)[0];
|
|
assert.equal(Number(pressureKey), 100000);
|
|
assert.ok(Math.abs(out.nq[pressureKey].y[1] - 1) < 1e-9, `expected 1 m3/s got ${out.nq[pressureKey].y[1]}`);
|
|
assert.ok(Math.abs(out.np[pressureKey].y[1] - 1000) < 1e-6, `expected 1000 W got ${out.np[pressureKey].y[1]}`);
|
|
});
|
|
|
|
test('normalizeCurveSection: warns on cross-pressure median > 3x jump', () => {
|
|
const policy = makePolicy();
|
|
const logger = captureLogger();
|
|
const section = {
|
|
1000: { x: [0, 50, 100], y: [0, 5, 10] }, // median 5
|
|
1100: { x: [0, 50, 100], y: [0, 50, 100] }, // median 50 (10x jump)
|
|
};
|
|
normalizeCurveSection(section, policy, 'm3/h', 'm3/h', 'mbar', 'mbar', 'nq', logger);
|
|
const hit = logger.warns.find((w) => /Curve anomaly/.test(w));
|
|
assert.ok(hit, `expected a Curve anomaly warning, got: ${JSON.stringify(logger.warns)}`);
|
|
assert.match(hit, /pressure 1100/);
|
|
});
|
|
|
|
test('normalizeCurveSection: does not warn on smooth progressions', () => {
|
|
const policy = makePolicy();
|
|
const logger = captureLogger();
|
|
const section = {
|
|
1000: { x: [0, 50, 100], y: [0, 5, 10] },
|
|
1100: { x: [0, 50, 100], y: [0, 6, 11] },
|
|
};
|
|
normalizeCurveSection(section, policy, 'm3/h', 'm3/h', 'mbar', 'mbar', 'nq', logger);
|
|
assert.equal(logger.warns.filter((w) => /Curve anomaly/.test(w)).length, 0);
|
|
});
|
|
|
|
test('normalizeCurveSection: throws when x/y length mismatch', () => {
|
|
const policy = makePolicy();
|
|
assert.throws(
|
|
() => normalizeCurveSection({ 1000: { x: [0, 50], y: [0, 5, 10] } }, policy, 'm3/h', 'm3/s', 'mbar', 'Pa', 'nq', null),
|
|
/Invalid nq section/
|
|
);
|
|
});
|