// Basin geometry for a wet-well pumping station. // // Models the basin as a rectangular prism (constant cross-section), so // volume = level × surfaceArea. Owns the level↔volume conversions and the // derived threshold volumes used by control + safety. Pure domain — no // Node-RED, no logger, no side effects beyond construction. class BasinGeometry { /** * @param {object} basinConfig - { volume, height, inflowLevel, outflowLevel, overflowLevel } * @param {object} hydraulicsConfig - { minHeightBasedOn: 'inlet' | 'outlet' } */ constructor(basinConfig, hydraulicsConfig) { const volEmptyBasin = basinConfig.volume; const heightBasin = basinConfig.height; const inflowLevel = basinConfig.inflowLevel; const outflowLevel = basinConfig.outflowLevel; const overflowLevel = basinConfig.overflowLevel; const inletPipeDiameter = basinConfig.inletPipeDiameter; const outletPipeDiameter = basinConfig.outletPipeDiameter; const minHeightBasedOn = hydraulicsConfig?.minHeightBasedOn; const surfaceArea = volEmptyBasin / heightBasin; // maxVol ≡ volEmptyBasin under the constant cross-section assumption; // kept as a separate field for naming symmetry with the trigger volumes. const maxVol = heightBasin * surfaceArea; const maxVolAtOverflow = overflowLevel * surfaceArea; const minVolAtOutflow = outflowLevel * surfaceArea; const minVolAtInflow = inflowLevel * surfaceArea; const minVol = minHeightBasedOn === 'inlet' ? minVolAtInflow : minVolAtOutflow; this._volEmptyBasin = volEmptyBasin; this._heightBasin = heightBasin; this._inflowLevel = inflowLevel; this._outflowLevel = outflowLevel; this._overflowLevel = overflowLevel; this._inletPipeDiameter = inletPipeDiameter; this._outletPipeDiameter = outletPipeDiameter; this._surfaceArea = surfaceArea; this._maxVol = maxVol; this._maxVolAtOverflow = maxVolAtOverflow; this._minVolAtInflow = minVolAtInflow; this._minVolAtOutflow = minVolAtOutflow; this._minVol = minVol; this._minHeightBasedOn = minHeightBasedOn; } get volEmptyBasin() { return this._volEmptyBasin; } get heightBasin() { return this._heightBasin; } get inflowLevel() { return this._inflowLevel; } get outflowLevel() { return this._outflowLevel; } get overflowLevel() { return this._overflowLevel; } get inletPipeDiameter() { return this._inletPipeDiameter; } get outletPipeDiameter() { return this._outletPipeDiameter; } get surfaceArea() { return this._surfaceArea; } get maxVol() { return this._maxVol; } get maxVolAtOverflow() { return this._maxVolAtOverflow; } get minVolAtInflow() { return this._minVolAtInflow; } get minVolAtOutflow() { return this._minVolAtOutflow; } get minVol() { return this._minVol; } get minHeightBasedOn() { return this._minHeightBasedOn; } /** Convert level (m from floor) → volume (m3). Negative levels clamp to 0. */ volumeFromLevel(level) { return Math.max(level, 0) * this._surfaceArea; } /** Convert volume (m3) → level (m from floor). Negative volumes clamp to 0. */ levelFromVolume(volume) { return Math.max(volume, 0) / this._surfaceArea; } /** * Plain-object snapshot mirroring the legacy `this.basin` shape so * getOutput / status code can keep using the same field names without * caring whether it's holding a class instance or a plain object. */ snapshot() { return { volEmptyBasin: this._volEmptyBasin, heightBasin: this._heightBasin, inflowLevel: this._inflowLevel, outflowLevel: this._outflowLevel, overflowLevel: this._overflowLevel, inletPipeDiameter: this._inletPipeDiameter, outletPipeDiameter: this._outletPipeDiameter, surfaceArea: this._surfaceArea, maxVol: this._maxVol, maxVolAtOverflow: this._maxVolAtOverflow, minVolAtInflow: this._minVolAtInflow, minVolAtOutflow: this._minVolAtOutflow, minVol: this._minVol, minHeightBasedOn: this._minHeightBasedOn, }; } } module.exports = BasinGeometry;