Compare commits

...

1 Commits

Author SHA1 Message Date
znetsixe
ef81013e96 B1.2: drop legacy 'overfillLevel' alias from thresholdValidator
Decision 2026-05-11: 'highVolumeSafetyLevel' is canonical. The legacy
'overfillLevel' name is gone from computeSafetyPoints + the validator
issue tuple. 'overfillVol' parallel alias kept (out of scope for this
task; flagged for follow-up). 130/130 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:13:21 +02:00
2 changed files with 19 additions and 24 deletions

View File

@@ -19,8 +19,8 @@ function computeSafetyPoints(basin, safety = {}) {
const dryRunPct = Number(safety.dryRunThresholdPercent) || 0; const dryRunPct = Number(safety.dryRunThresholdPercent) || 0;
const rawHighPct = safety.highVolumeSafetyThresholdPercent ?? safety.overfillThresholdPercent; const rawHighPct = safety.highVolumeSafetyThresholdPercent ?? safety.overfillThresholdPercent;
// When neither high-volume nor overfill pct is supplied, use 100 % so // When neither high-volume nor overfill pct is supplied, use 100 % so
// the validator's `maxLevel <= overfillLevel` check is a no-op (the // the validator's `maxLevel <= highVolumeSafetyLevel` check is a no-op
// basin can't physically exceed overflow anyway). Tests pin this. // (the basin can't physically exceed overflow anyway). Tests pin this.
const highPct = Number(rawHighPct); const highPct = Number(rawHighPct);
const effectiveHighPct = Number.isFinite(highPct) ? highPct : 100; const effectiveHighPct = Number.isFinite(highPct) ? highPct : 100;
const minVol = Number(basin?.minVol) || 0; const minVol = Number(basin?.minVol) || 0;
@@ -41,7 +41,6 @@ function computeSafetyPoints(basin, safety = {}) {
highVolumeSafetyVol, highVolumeSafetyVol,
highVolumeSafetyLevel, highVolumeSafetyLevel,
// Back-compat alias — pre-basin-docs name. // Back-compat alias — pre-basin-docs name.
overfillLevel: highVolumeSafetyLevel,
overfillVol: highVolumeSafetyVol, overfillVol: highVolumeSafetyVol,
}; };
} }
@@ -55,22 +54,17 @@ function computeSafetyPoints(basin, safety = {}) {
function validateThresholdOrdering(basin, levelbased, safety) { function validateThresholdOrdering(basin, levelbased, safety) {
const lvl = levelbased || {}; const lvl = levelbased || {};
const points = computeSafetyPoints(basin, safety); const points = computeSafetyPoints(basin, safety);
const { dryRunLevel, overfillLevel } = points; const { dryRunLevel, highVolumeSafetyLevel } = points;
// basin-docs added `startLevel <= inflowLevel` and `inflowLevel <
// maxLevel`; HEAD had only the `startLevel < maxLevel` and
// `maxLevel <= overfillLevel` checks. We keep the `overfillLevel`
// name (rather than basin-docs's `highVolumeSafetyLevel`) for
// back-compat with consumers reading issue.bName.
const checks = [ const checks = [
['outflowLevel', basin.outflowLevel, '<', 'inflowLevel', basin.inflowLevel], ['outflowLevel', basin.outflowLevel, '<', 'inflowLevel', basin.inflowLevel],
['inflowLevel', basin.inflowLevel, '<', 'overflowLevel', basin.overflowLevel], ['inflowLevel', basin.inflowLevel, '<', 'overflowLevel', basin.overflowLevel],
['overflowLevel', basin.overflowLevel, '<=', 'basinHeight', basin.heightBasin], ['overflowLevel', basin.overflowLevel, '<=', 'basinHeight', basin.heightBasin],
['dryRunLevel', dryRunLevel, '<=', 'minLevel', lvl.minLevel], ['dryRunLevel', dryRunLevel, '<=', 'minLevel', lvl.minLevel],
['minLevel', lvl.minLevel, '<=', 'startLevel', lvl.startLevel], ['minLevel', lvl.minLevel, '<=', 'startLevel', lvl.startLevel],
['startLevel', lvl.startLevel, '<=', 'inflowLevel', basin.inflowLevel], ['startLevel', lvl.startLevel, '<=', 'inflowLevel', basin.inflowLevel],
['startLevel', lvl.startLevel, '<', 'maxLevel', lvl.maxLevel], ['startLevel', lvl.startLevel, '<', 'maxLevel', lvl.maxLevel],
['maxLevel', lvl.maxLevel, '<=', 'overfillLevel', overfillLevel], ['maxLevel', lvl.maxLevel, '<=', 'highVolumeSafetyLevel', highVolumeSafetyLevel],
]; ];
const issues = []; const issues = [];

View File

@@ -8,7 +8,8 @@ const { validateThresholdOrdering } = require('../../src/basin/thresholdValidato
const BasinGeometry = require('../../src/basin/BasinGeometry'); const BasinGeometry = require('../../src/basin/BasinGeometry');
// A valid baseline: outlet 0.2 < inflow 3 < overflow 4.5 ≤ height 5, // A valid baseline: outlet 0.2 < inflow 3 < overflow 4.5 ≤ height 5,
// dryRun = 0.2 * 1.10 = 0.22 ≤ minLevel 1 ≤ start 2 < max 4 ≤ overfill 4.275. // dryRun = 0.2 * 1.10 = 0.22 ≤ minLevel 1 ≤ start 2 < max 4
// ≤ highVolumeSafetyLevel 4.275.
function validBasinAndCfg() { function validBasinAndCfg() {
const basin = new BasinGeometry( const basin = new BasinGeometry(
{ volume: 50, height: 5, inflowLevel: 3, outflowLevel: 0.2, overflowLevel: 4.5 }, { volume: 50, height: 5, inflowLevel: 3, outflowLevel: 0.2, overflowLevel: 4.5 },
@@ -40,17 +41,17 @@ test('outflowLevel >= inflowLevel triggers issue with correct shape', () => {
assert.match(hit.msg, /outflowLevel.*<.*inflowLevel/); assert.match(hit.msg, /outflowLevel.*<.*inflowLevel/);
}); });
test('maxLevel >= overfillLevel triggers issue', () => { test('maxLevel >= highVolumeSafetyLevel triggers issue', () => {
const { basin } = validBasinAndCfg(); const { basin } = validBasinAndCfg();
// overfillLevel = overflowLevel × overfillPct/100 = 4.5 × 0.80 = 3.6. // highVolumeSafetyLevel = overflowLevel × highPct/100 = 4.5 × 0.80 = 3.6.
// maxLevel 4 > 3.6 → expect a `maxLevel <= overfillLevel` issue. // maxLevel 4 > 3.6 → expect a `maxLevel <= highVolumeSafetyLevel` issue.
const issues = validateThresholdOrdering( const issues = validateThresholdOrdering(
basin, basin,
{ minLevel: 1, startLevel: 2, maxLevel: 4 }, { minLevel: 1, startLevel: 2, maxLevel: 4 },
{ dryRunThresholdPercent: 10, overfillThresholdPercent: 80 } { dryRunThresholdPercent: 10, overfillThresholdPercent: 80 }
); );
const hit = issues.find((i) => i.aName === 'maxLevel' && i.bName === 'overfillLevel'); const hit = issues.find((i) => i.aName === 'maxLevel' && i.bName === 'highVolumeSafetyLevel');
assert.ok(hit, 'expected a maxLevel <= overfillLevel issue'); assert.ok(hit, 'expected a maxLevel <= highVolumeSafetyLevel issue');
assert.equal(hit.op, '<='); assert.equal(hit.op, '<=');
assert.equal(hit.a, 4); assert.equal(hit.a, 4);
assert.ok(Math.abs(hit.b - 3.6) < 1e-9); assert.ok(Math.abs(hit.b - 3.6) < 1e-9);
@@ -66,7 +67,7 @@ test('NaN / undefined values are skipped, not flagged as issues', () => {
// dryRunLevel <= minLevel skipped (minLevel undefined → NaN) // dryRunLevel <= minLevel skipped (minLevel undefined → NaN)
// minLevel <= startLevel skipped (both NaN-ish) // minLevel <= startLevel skipped (both NaN-ish)
// startLevel < maxLevel skipped (startLevel NaN) // startLevel < maxLevel skipped (startLevel NaN)
// maxLevel <= overfillLevel still checked → 4 ≤ 4.275 OK. // maxLevel <= highVolumeSafetyLevel still checked → 4 ≤ 4.275 OK.
// Geometry checks also OK. // Geometry checks also OK.
assert.deepEqual(issues, []); assert.deepEqual(issues, []);
}); });