2026-05-10 20:18:49 +02:00
|
|
|
|
// 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;
|
2026-05-11 16:19:55 +02:00
|
|
|
|
const inletPipeDiameter = basinConfig.inletPipeDiameter;
|
|
|
|
|
|
const outletPipeDiameter = basinConfig.outletPipeDiameter;
|
2026-05-10 20:18:49 +02:00
|
|
|
|
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;
|
2026-05-11 16:19:55 +02:00
|
|
|
|
this._inletPipeDiameter = inletPipeDiameter;
|
|
|
|
|
|
this._outletPipeDiameter = outletPipeDiameter;
|
2026-05-10 20:18:49 +02:00
|
|
|
|
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; }
|
2026-05-11 16:19:55 +02:00
|
|
|
|
get inletPipeDiameter() { return this._inletPipeDiameter; }
|
|
|
|
|
|
get outletPipeDiameter() { return this._outletPipeDiameter; }
|
2026-05-10 20:18:49 +02:00
|
|
|
|
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,
|
2026-05-11 16:19:55 +02:00
|
|
|
|
inletPipeDiameter: this._inletPipeDiameter,
|
|
|
|
|
|
outletPipeDiameter: this._outletPipeDiameter,
|
2026-05-10 20:18:49 +02:00
|
|
|
|
surfaceArea: this._surfaceArea,
|
|
|
|
|
|
maxVol: this._maxVol,
|
|
|
|
|
|
maxVolAtOverflow: this._maxVolAtOverflow,
|
|
|
|
|
|
minVolAtInflow: this._minVolAtInflow,
|
|
|
|
|
|
minVolAtOutflow: this._minVolAtOutflow,
|
|
|
|
|
|
minVol: this._minVol,
|
|
|
|
|
|
minHeightBasedOn: this._minHeightBasedOn,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = BasinGeometry;
|