Compare commits

...

1 Commits

5 changed files with 169 additions and 9 deletions

View File

@@ -14,6 +14,7 @@ const validation = require('./src/helper/validationUtils.js');
const configUtils = require('./src/helper/configUtils.js');
const assertions = require('./src/helper/assertionUtils.js')
const coolprop = require('./src/coolprop-node/src/index.js');
const gravity = require('./src/helper/gravity.js')
// Domain-specific modules
const { MeasurementContainer } = require('./src/measurements/index.js');
@@ -44,5 +45,6 @@ module.exports = {
convert,
MenuManager,
childRegistrationUtils,
loadCurve
loadCurve,
gravity
};

View File

@@ -409,7 +409,7 @@
}
},
"timeThresholdSeconds": {
"default": 120,
"default": 5,
"rules": {
"type": "number",
"min": 0,

View File

@@ -3,11 +3,61 @@ const customRefs = require('./refData.js');
class CoolPropWrapper {
constructor() {
this.initialized = false;
this.defaultRefrigerant = null;
this.defaultTempUnit = 'K'; // K, C, F
this.defaultPressureUnit = 'Pa' // Pa, kPa, bar, psi
this.customRef = false;
this.PropsSI = this._propsSI.bind(this);
// 🔹 Wastewater correction options (defaults)
this._ww = {
enabled: true,
tss_g_per_L: 3.5, // default MLSS / TSS
density_k: 2e-4, // +0.02% per g/L
viscosity_k: 0.07, // +7% per g/L (clamped)
viscosity_max_gpl: 4 // cap effect at 4 g/L
};
this._initPromise = null;
this._autoInit({ refrigerant: 'Water' });
}
_isWastewaterFluid(fluidRaw) {
if (!fluidRaw) return false;
const token = String(fluidRaw).trim().toLowerCase();
return token === 'wastewater' || token.startsWith('wastewater:');
}
_parseWastewaterFluid(fluidRaw) {
if (!this._isWastewaterFluid(fluidRaw)) return null;
const ww = { ...this._ww };
const [, tail] = String(fluidRaw).split(':');
if (tail) {
tail.split(',').forEach(pair => {
const [key, value] = pair.split('=').map(s => s.trim().toLowerCase());
if (key === 'tss' && !Number.isNaN(Number(value))) {
ww.tss_g_per_L = Number(value);
}
});
}
return ww;
}
_applyWastewaterCorrection(outputKey, baseValue, ww) {
if (!Number.isFinite(baseValue) || !ww || !ww.enabled) return baseValue;
switch (outputKey.toUpperCase()) {
case 'D': // density
return baseValue * (1 + ww.density_k * ww.tss_g_per_L);
case 'V': // viscosity
const effTss = Math.min(ww.tss_g_per_L, ww.viscosity_max_gpl);
return baseValue * (1 + ww.viscosity_k * effTss);
default:
return baseValue;
}
}
// Temperature conversion helpers
@@ -407,13 +457,31 @@ class CoolPropWrapper {
}
}
// Direct access to CoolProp functions
async getPropsSI() {
_autoInit(defaults) {
if (!this._initPromise) {
this._initPromise = this.init(defaults);
}
return this._initPromise;
}
_propsSI(outputKey, inKey1, inVal1, inKey2, inVal2, fluidRaw) {
if (!this.initialized) {
await coolprop.init();
// Start init if no one else asked yet
this._autoInit({ refrigerant: this.defaultRefrigerant || 'Water' });
throw new Error('CoolProp is still warming up, retry PropsSI in a moment');
}
return coolprop.PropsSI;
const ww = this._parseWastewaterFluid(fluidRaw);
const fluid = ww ? 'Water' : (this.customRefString || fluidRaw);
const baseValue = coolprop.PropsSI(outputKey, inKey1, inVal1, inKey2, inVal2, fluid);
return ww ? this._applyWastewaterCorrection(outputKey, baseValue, ww) : baseValue;
}
//Access to coolprop
async getPropsSI() {
await this._ensureInit({ refrigerant: this.defaultRefrigerant || 'Water' });
return this.PropsSI;
}
}
module.exports = new CoolPropWrapper();

90
src/helper/gravity.js Normal file
View File

@@ -0,0 +1,90 @@
/**
* Gravity calculations based on WGS-84 ellipsoid model.
* Author: Rene de Ren (Waterschap Brabantse Delta)
* License: EUPL-1.2
*/
class Gravity {
constructor() {
// Standard (conventional) gravity at 45° latitude, sea level
this.g0 = 9.80665; // m/s²
}
/**
* Returns standard gravity (constant)
* @returns {number} gravity in m/s²
*/
getStandardGravity() {
return this.g0;
}
/**
* Computes local gravity based on latitude and elevation.
* Formula: WGS-84 normal gravity (Somigliana)
* @param {number} latitudeDeg Latitude in degrees (90 → +90)
* @param {number} elevationM Elevation above sea level [m]
* @returns {number} gravity in m/s²
*/
getLocalGravity(latitudeDeg, elevationM = 0) {
const phi = (latitudeDeg * Math.PI) / 180;
const sinPhi = Math.sin(phi);
const sin2 = sinPhi * sinPhi;
const sin2_2phi = Math.sin(2 * phi) ** 2;
// WGS-84 normal gravity on the ellipsoid
const gSurface =
9.780327 * (1 + 0.0053024 * sin2 - 0.0000058 * sin2_2phi);
// Free-air correction for elevation (~ 3.086×10⁻⁶ m/s² per m)
const gLocal = gSurface - 3.086e-6 * elevationM;
return gLocal;
}
/**
* Calculates hydrostatic pressure difference (ΔP = ρ g h)
* @param {number} density Fluid density [kg/m³]
* @param {number} heightM Height difference [m]
* @param {number} latitudeDeg Latitude (for local g)
* @param {number} elevationM Elevation (for local g)
* @returns {number} Pressure difference [Pa]
*/
pressureHead(density, heightM, latitudeDeg = 45, elevationM = 0) {
const g = this.getLocalGravity(latitudeDeg, elevationM);
return density * g * heightM;
}
/**
* Calculates weight force (F = m g)
* @param {number} massKg Mass [kg]
* @param {number} latitudeDeg Latitude (for local g)
* @param {number} elevationM Elevation (for local g)
* @returns {number} Force [N]
*/
weightForce(massKg, latitudeDeg = 45, elevationM = 0) {
const g = this.getLocalGravity(latitudeDeg, elevationM);
return massKg * g;
}
}
module.exports = new Gravity();
/*
const gravity = gravity;
// Standard gravity
console.log('g₀ =', gravity.getStandardGravity(), 'm/s²');
// Local gravity (Breda ≈ 51.6° N, 3 m elevation)
console.log('g @ Breda =', gravity.getLocalGravity(51.6, 3).toFixed(6), 'm/s²');
// Head pressure for 5 m water column at Breda
console.log(
'ΔP =',
gravity.pressureHead(1000, 5, 51.6, 3).toFixed(1),
'Pa'
);
// Weight of 1 kg mass at Breda
console.log('Weight =', gravity.weightForce(1, 51.6, 3).toFixed(6), 'N');
*/

View File

@@ -6,9 +6,9 @@ class PhysicalPositionMenu {
return {
positionGroups: [
{ group: 'Positional', options: [
{ value: 'upstream', label: '→ Upstream', icon: ''}, //flow is then typically left to right
{ value: 'upstream', label: '→ Upstream', icon: ''}, //flow is then typically left to right
{ value: 'atEquipment', label: '⊥ in place' , icon: '⊥' },
{ value: 'downstream', label: '← Downstream' , icon: '' }
{ value: 'downstream', label: '← Downstream' , icon: '' }
]
}
],