heatingpro-efficiency
Version:
Utility functions for MonitoringPro efficiency calculations with flexible unit support (GJ, kWh, MWh)
246 lines (208 loc) • 7.87 kB
JavaScript
const {
hasPropertiesAndNotDash,
toFloatWithDot,
convertHeatToKwh,
} = require("./utils/index.js");
const computeEfficiency = (
row,
prevRow,
monthlyEffectivityConstant,
fieldUnits = {}
) => {
// Helper function to convert MWh to GJ if needed
const convertValue = (value, fieldName) => {
if (fieldUnits[fieldName] === "mwh") {
return value * 3.6; // Convert MWh to GJ
}
return value;
};
let sumOfDiffs = 0;
let hasNegativeDiff = false;
let totalPreviousHeat = 0;
for (let i = 1; i <= 8; i++) {
const voKey = `VO${i}`;
if (hasPropertiesAndNotDash(row, prevRow, voKey)) {
const currentVal = toFloatWithDot(row[voKey]);
const prevVal = toFloatWithDot(prevRow[voKey]);
// Convert values if they are in MWh
const convertedCurrentVal = convertValue(currentVal, voKey);
const convertedPrevVal = convertValue(prevVal, voKey);
const diff = convertedCurrentVal - convertedPrevVal;
if (!isNaN(diff)) {
if (diff < 0) {
hasNegativeDiff = true;
}
totalPreviousHeat += convertedPrevVal;
sumOfDiffs += diff;
}
}
}
if (hasPropertiesAndNotDash(row, prevRow, "MT TUV")) {
const currentVal = toFloatWithDot(row["MT TUV"]);
const prevVal = toFloatWithDot(prevRow["MT TUV"]);
// Convert values if they are in MWh
const convertedCurrentVal = convertValue(currentVal, "MT TUV");
const convertedPrevVal = convertValue(prevVal, "MT TUV");
const diff = convertedCurrentVal - convertedPrevVal;
if (!isNaN(diff)) {
if (diff < 0) {
hasNegativeDiff = true;
}
totalPreviousHeat += convertedPrevVal;
sumOfDiffs += diff;
}
}
// Detect heat meter reset: negative difference that's significant (> 50% of previous total)
// This indicates the meter was reset (current reading < previous reading by a large margin)
const isHeatMeterReset = hasNegativeDiff && totalPreviousHeat > 0 && sumOfDiffs < 0 && Math.abs(sumOfDiffs) > totalPreviousHeat * 0.5;
const gasDiff = toFloatWithDot(row["Plyn"]) - toFloatWithDot(prevRow["Plyn"]);
// Handle gas meter reset: if current reading is significantly less than previous,
// it's likely a meter reset. A reset is detected when current < previous AND
// the difference is large (> 50% of previous value)
const prevGasValue = toFloatWithDot(prevRow["Plyn"]);
const isGasMeterReset = gasDiff < 0 && prevGasValue > 0 && Math.abs(gasDiff) > prevGasValue * 0.5;
// Convert heat to kWh based on input units (always GJ now since we converted above)
const voDiffInKwh = convertHeatToKwh(sumOfDiffs, "GJ");
const gasDiffInKwh = gasDiff * 9.855; // 1 m³ gas = 9.855 kWh (Method 2)
let efficiency;
// Handle different scenarios for efficiency calculation
if (isGasMeterReset || isHeatMeterReset) {
// Meter reset detected - cannot calculate efficiency reliably
efficiency = "-";
} else if (gasDiff <= 0) {
// Negative or zero gas consumption - invalid
if (gasDiff < 0) {
// Negative gas difference indicates meter reset or data error
efficiency = "-";
} else if (sumOfDiffs === 0) {
// No consumption at all - no meaningful efficiency
efficiency = "-";
} else {
// Heat produced without gas consumption - invalid scenario
efficiency = "-";
}
} else if (sumOfDiffs <= 0) {
// Negative or zero heat production
if (sumOfDiffs < 0) {
// Negative heat difference indicates meter reset or data error
efficiency = "-";
} else {
// Gas consumed but no heat produced - 0% efficiency
efficiency = "0.0000";
}
} else if (monthlyEffectivityConstant <= 0 || isNaN(monthlyEffectivityConstant)) {
// Invalid effectivity constant
efficiency = "-";
} else {
// Normal calculation: efficiency = (heat output / (gas input * constant))
// Returns decimal value (e.g., 0.88 for 88%) - will be multiplied by 100 in display code
const calculatedEfficiency = voDiffInKwh / (gasDiffInKwh * monthlyEffectivityConstant);
// Validate result: efficiency should be reasonable
// Check for NaN, Infinity, or negative values
// Note: Upper bound removed as efficiency can vary based on effectivityConstant value
if (isNaN(calculatedEfficiency) || !isFinite(calculatedEfficiency) || calculatedEfficiency < 0) {
efficiency = "-";
} else {
efficiency = Number(calculatedEfficiency).toFixed(4);
}
}
// Return heat units value (always in GJ for display)
// Make sure we don't return NaN
const heatUnitsValue = isNaN(sumOfDiffs) ? 0 : sumOfDiffs;
return {
...row,
ucinnost: efficiency,
heatUnits: heatUnitsValue,
};
};
/**
* Checks if heat meters are being written off (have valid readings and differences)
* @param {Array} valuesData - Array of row data objects
* @param {Object} fieldUnits - Mapping of field names to their units
* @returns {boolean} - True if meters exist and are being written off, false otherwise
*/
const areMetersBeingWrittenOff = (valuesData, fieldUnits = {}) => {
if (!valuesData || valuesData.length < 2) {
return false;
}
// Helper function to convert MWh to GJ if needed
const convertValue = (value, fieldName) => {
if (fieldUnits[fieldName] === "mwh") {
return value * 3.6; // Convert MWh to GJ
}
return value;
};
// First, check if meters exist (have non-dash values in any row)
let metersExist = false;
for (const row of valuesData) {
// Check VO1-VO8 meters
for (let j = 1; j <= 8; j++) {
const voKey = `VO${j}`;
if (
row.hasOwnProperty(voKey) &&
row[voKey] !== "-" &&
row[voKey] !== null &&
row[voKey] !== undefined &&
row[voKey] !== ""
) {
metersExist = true;
break;
}
}
// Check MT TUV meter
if (
row.hasOwnProperty("MT TUV") &&
row["MT TUV"] !== "-" &&
row["MT TUV"] !== null &&
row["MT TUV"] !== undefined &&
row["MT TUV"] !== ""
) {
metersExist = true;
}
if (metersExist) break;
}
// If meters don't exist, return false
if (!metersExist) {
return false;
}
// Check if there are any valid meter readings with differences across rows
for (let i = 1; i < valuesData.length; i++) {
const row = valuesData[i];
const prevRow = valuesData[i - 1];
let sumOfDiffs = 0;
// Check VO1-VO8 meters
for (let j = 1; j <= 8; j++) {
const voKey = `VO${j}`;
if (hasPropertiesAndNotDash(row, prevRow, voKey)) {
const currentVal = toFloatWithDot(row[voKey]);
const prevVal = toFloatWithDot(prevRow[voKey]);
// Convert values if they are in MWh
const convertedCurrentVal = convertValue(currentVal, voKey);
const convertedPrevVal = convertValue(prevVal, voKey);
const diff = convertedCurrentVal - convertedPrevVal;
if (!isNaN(diff) && diff !== 0) {
sumOfDiffs += Math.abs(diff);
}
}
}
// Check MT TUV meter
if (hasPropertiesAndNotDash(row, prevRow, "MT TUV")) {
const currentVal = toFloatWithDot(row["MT TUV"]);
const prevVal = toFloatWithDot(prevRow["MT TUV"]);
// Convert values if they are in MWh
const convertedCurrentVal = convertValue(currentVal, "MT TUV");
const convertedPrevVal = convertValue(prevVal, "MT TUV");
const diff = convertedCurrentVal - convertedPrevVal;
if (!isNaN(diff) && diff !== 0) {
sumOfDiffs += Math.abs(diff);
}
}
// If we found any non-zero difference, meters are being written off
if (sumOfDiffs > 0) {
return true;
}
}
// Meters exist but no differences found (not being written off yet)
return false;
};
module.exports = { computeEfficiency, areMetersBeingWrittenOff };