heatingpro-efficiency-package 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # MonitoringPro Utils
2
+
3
+ Utility functions for MonitoringPro efficiency calculations with flexible unit support.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install heatingpro-efficiency
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```javascript
14
+ const { computeEfficiency, areMetersBeingWrittenOff } = require('heatingpro-efficiency');
15
+
16
+ // Compute efficiency for data in GJ (default)
17
+ const result1 = computeEfficiency(row, prevRow, monthlyEffectivityConstant);
18
+
19
+ // Compute efficiency with field units mapping
20
+ const result2 = computeEfficiency(row, prevRow, monthlyEffectivityConstant, fieldUnits);
21
+
22
+ // Check if meters are being written off
23
+ const metersActive = areMetersBeingWrittenOff(valuesData, fieldUnits);
24
+ ```
25
+
26
+ ## Functions
27
+
28
+ ### `computeEfficiency(row, prevRow, monthlyEffectivityConstant, fieldUnits)`
29
+
30
+ Computes efficiency based on heat meter readings and gas consumption.
31
+
32
+ **Parameters:**
33
+ - `row` - Current row data object
34
+ - `prevRow` - Previous row data object
35
+ - `monthlyEffectivityConstant` - Monthly effectivity constant value
36
+ - `fieldUnits` (optional) - Object mapping field names to their units (e.g., `{ "VO1": "mwh" }`)
37
+
38
+ **Returns:** Object with `ucinnost` (efficiency) and `heatUnits` values
39
+
40
+ ### `areMetersBeingWrittenOff(valuesData, fieldUnits)`
41
+
42
+ Checks if heat meters (VO1-VO8, MT TUV) exist and are being written off.
43
+
44
+ **Parameters:**
45
+ - `valuesData` - Array of row data objects
46
+ - `fieldUnits` (optional) - Object mapping field names to their units
47
+
48
+ **Returns:** `true` if meters exist and are being written off, `false` otherwise
49
+
50
+ ## Supported Heat Units
51
+
52
+ - **GJ** (Gigajoules) - Default unit, converts using 1 GJ = 277.778 kWh
53
+ - **kWh** (Kilowatt-hours) - No conversion needed
54
+ - **MWh** (Megawatt-hours) - Converts using 1 MWh = 1000 kWh
55
+
56
+ ## Version History
57
+
58
+ - **1.0.8** - Fixed efficiency calculation bug:
59
+ - Removed incorrect `* 10` multiplier that caused efficiency values to be 10x too high
60
+ - Added meter reset detection for gas and heat meters
61
+ - Improved validation for negative differences and edge cases
62
+ - Added comprehensive test suite
63
+ - **1.0.7** - Added `areMetersBeingWrittenOff` function to check if heat meters exist and are being written off
64
+ - **1.0.6** - Previous version
65
+ - **1.0.5** - Added flexible unit support for heat calculations
66
+
67
+ ## Testing
68
+
69
+ Run tests with:
70
+ ```bash
71
+ npm test
72
+ ```
73
+
74
+ Run tests in watch mode:
75
+ ```bash
76
+ npm run test:watch
77
+ ```
78
+
79
+ Generate coverage report:
80
+ ```bash
81
+ npm run test:coverage
82
+ ```
83
+
84
+ ## License
85
+
86
+ ISC
package/index.js ADDED
@@ -0,0 +1,245 @@
1
+ const {
2
+ hasPropertiesAndNotDash,
3
+ toFloatWithDot,
4
+ convertHeatToKwh,
5
+ } = require("./utils/index.js");
6
+
7
+ const computeEfficiency = (
8
+ row,
9
+ prevRow,
10
+ monthlyEffectivityConstant,
11
+ fieldUnits = {}
12
+ ) => {
13
+ // Helper function to convert MWh to GJ if needed
14
+ const convertValue = (value, fieldName) => {
15
+ if (fieldUnits[fieldName] === "mwh") {
16
+ return value * 3.6; // Convert MWh to GJ
17
+ }
18
+ return value;
19
+ };
20
+
21
+ let sumOfDiffs = 0;
22
+ let hasNegativeDiff = false;
23
+ let totalPreviousHeat = 0;
24
+
25
+ for (let i = 1; i <= 8; i++) {
26
+ const voKey = `VO${i}`;
27
+
28
+ if (hasPropertiesAndNotDash(row, prevRow, voKey)) {
29
+ const currentVal = toFloatWithDot(row[voKey]);
30
+ const prevVal = toFloatWithDot(prevRow[voKey]);
31
+
32
+ // Convert values if they are in MWh
33
+ const convertedCurrentVal = convertValue(currentVal, voKey);
34
+ const convertedPrevVal = convertValue(prevVal, voKey);
35
+
36
+ const diff = convertedCurrentVal - convertedPrevVal;
37
+
38
+ if (!isNaN(diff)) {
39
+ if (diff < 0) {
40
+ hasNegativeDiff = true;
41
+ }
42
+ totalPreviousHeat += convertedPrevVal;
43
+ sumOfDiffs += diff;
44
+ }
45
+ }
46
+ }
47
+
48
+ if (hasPropertiesAndNotDash(row, prevRow, "MT TUV")) {
49
+ const currentVal = toFloatWithDot(row["MT TUV"]);
50
+ const prevVal = toFloatWithDot(prevRow["MT TUV"]);
51
+
52
+ // Convert values if they are in MWh
53
+ const convertedCurrentVal = convertValue(currentVal, "MT TUV");
54
+ const convertedPrevVal = convertValue(prevVal, "MT TUV");
55
+
56
+ const diff = convertedCurrentVal - convertedPrevVal;
57
+
58
+ if (!isNaN(diff)) {
59
+ if (diff < 0) {
60
+ hasNegativeDiff = true;
61
+ }
62
+ totalPreviousHeat += convertedPrevVal;
63
+ sumOfDiffs += diff;
64
+ }
65
+ }
66
+
67
+ // Detect heat meter reset: negative difference that's significant (> 50% of previous total)
68
+ // This indicates the meter was reset (current reading < previous reading by a large margin)
69
+ const isHeatMeterReset = hasNegativeDiff && totalPreviousHeat > 0 && sumOfDiffs < 0 && Math.abs(sumOfDiffs) > totalPreviousHeat * 0.5;
70
+
71
+ const gasDiff = toFloatWithDot(row["Plyn"]) - toFloatWithDot(prevRow["Plyn"]);
72
+
73
+ // Handle gas meter reset: if current reading is significantly less than previous,
74
+ // it's likely a meter reset. A reset is detected when current < previous AND
75
+ // the difference is large (> 50% of previous value)
76
+ const prevGasValue = toFloatWithDot(prevRow["Plyn"]);
77
+ const isGasMeterReset = gasDiff < 0 && prevGasValue > 0 && Math.abs(gasDiff) > prevGasValue * 0.5;
78
+
79
+ // Convert heat to kWh based on input units (always GJ now since we converted above)
80
+ const voDiffInKwh = convertHeatToKwh(sumOfDiffs, "GJ");
81
+ const gasDiffInKwh = gasDiff * 9.855; // 1 m³ gas = 9.855 kWh (Method 2)
82
+
83
+ let efficiency;
84
+
85
+ // Handle different scenarios for efficiency calculation
86
+ if (isGasMeterReset || isHeatMeterReset) {
87
+ // Meter reset detected - cannot calculate efficiency reliably
88
+ efficiency = "-";
89
+ } else if (gasDiff <= 0) {
90
+ // Negative or zero gas consumption - invalid
91
+ if (gasDiff < 0) {
92
+ // Negative gas difference indicates meter reset or data error
93
+ efficiency = "-";
94
+ } else if (sumOfDiffs === 0) {
95
+ // No consumption at all - no meaningful efficiency
96
+ efficiency = "-";
97
+ } else {
98
+ // Heat produced without gas consumption - invalid scenario
99
+ efficiency = "-";
100
+ }
101
+ } else if (sumOfDiffs <= 0) {
102
+ // Negative or zero heat production
103
+ if (sumOfDiffs < 0) {
104
+ // Negative heat difference indicates meter reset or data error
105
+ efficiency = "-";
106
+ } else {
107
+ // Gas consumed but no heat produced - 0% efficiency
108
+ efficiency = "0.0000";
109
+ }
110
+ } else if (monthlyEffectivityConstant <= 0 || isNaN(monthlyEffectivityConstant)) {
111
+ // Invalid effectivity constant
112
+ efficiency = "-";
113
+ } else {
114
+ // Normal calculation: efficiency = (heat output / (gas input * constant))
115
+ // Returns decimal value (e.g., 0.88 for 88%) - will be multiplied by 100 in display code
116
+ const calculatedEfficiency = voDiffInKwh / (gasDiffInKwh * monthlyEffectivityConstant);
117
+
118
+ // Validate result: efficiency should be reasonable
119
+ // Check for NaN, Infinity, or negative values
120
+ // Note: Upper bound removed as efficiency can vary based on effectivityConstant value
121
+ if (isNaN(calculatedEfficiency) || !isFinite(calculatedEfficiency) || calculatedEfficiency < 0) {
122
+ efficiency = "-";
123
+ } else {
124
+ efficiency = Number(calculatedEfficiency).toFixed(4);
125
+ }
126
+ }
127
+
128
+ // Return heat units value (always in GJ for display)
129
+ // Make sure we don't return NaN
130
+ const heatUnitsValue = isNaN(sumOfDiffs) ? 0 : sumOfDiffs;
131
+
132
+ return {
133
+ ...row,
134
+ ucinnost: efficiency,
135
+ heatUnits: heatUnitsValue,
136
+ };
137
+ };
138
+
139
+ /**
140
+ * Checks if heat meters are being written off (have valid readings and differences)
141
+ * @param {Array} valuesData - Array of row data objects
142
+ * @param {Object} fieldUnits - Mapping of field names to their units
143
+ * @returns {boolean} - True if meters exist and are being written off, false otherwise
144
+ */
145
+ const areMetersBeingWrittenOff = (valuesData, fieldUnits = {}) => {
146
+ if (!valuesData || valuesData.length < 2) {
147
+ return false;
148
+ }
149
+
150
+ // Helper function to convert MWh to GJ if needed
151
+ const convertValue = (value, fieldName) => {
152
+ if (fieldUnits[fieldName] === "mwh") {
153
+ return value * 3.6; // Convert MWh to GJ
154
+ }
155
+ return value;
156
+ };
157
+
158
+ // First, check if meters exist (have non-dash values in any row)
159
+ let metersExist = false;
160
+ for (const row of valuesData) {
161
+ // Check VO1-VO8 meters
162
+ for (let j = 1; j <= 8; j++) {
163
+ const voKey = `VO${j}`;
164
+ if (
165
+ row.hasOwnProperty(voKey) &&
166
+ row[voKey] !== "-" &&
167
+ row[voKey] !== null &&
168
+ row[voKey] !== undefined &&
169
+ row[voKey] !== ""
170
+ ) {
171
+ metersExist = true;
172
+ break;
173
+ }
174
+ }
175
+ // Check MT TUV meter
176
+ if (
177
+ row.hasOwnProperty("MT TUV") &&
178
+ row["MT TUV"] !== "-" &&
179
+ row["MT TUV"] !== null &&
180
+ row["MT TUV"] !== undefined &&
181
+ row["MT TUV"] !== ""
182
+ ) {
183
+ metersExist = true;
184
+ }
185
+ if (metersExist) break;
186
+ }
187
+
188
+ // If meters don't exist, return false
189
+ if (!metersExist) {
190
+ return false;
191
+ }
192
+
193
+ // Check if there are any valid meter readings with differences across rows
194
+ for (let i = 1; i < valuesData.length; i++) {
195
+ const row = valuesData[i];
196
+ const prevRow = valuesData[i - 1];
197
+ let sumOfDiffs = 0;
198
+
199
+ // Check VO1-VO8 meters
200
+ for (let j = 1; j <= 8; j++) {
201
+ const voKey = `VO${j}`;
202
+
203
+ if (hasPropertiesAndNotDash(row, prevRow, voKey)) {
204
+ const currentVal = toFloatWithDot(row[voKey]);
205
+ const prevVal = toFloatWithDot(prevRow[voKey]);
206
+
207
+ // Convert values if they are in MWh
208
+ const convertedCurrentVal = convertValue(currentVal, voKey);
209
+ const convertedPrevVal = convertValue(prevVal, voKey);
210
+
211
+ const diff = convertedCurrentVal - convertedPrevVal;
212
+
213
+ if (!isNaN(diff) && diff !== 0) {
214
+ sumOfDiffs += Math.abs(diff);
215
+ }
216
+ }
217
+ }
218
+
219
+ // Check MT TUV meter
220
+ if (hasPropertiesAndNotDash(row, prevRow, "MT TUV")) {
221
+ const currentVal = toFloatWithDot(row["MT TUV"]);
222
+ const prevVal = toFloatWithDot(prevRow["MT TUV"]);
223
+
224
+ // Convert values if they are in MWh
225
+ const convertedCurrentVal = convertValue(currentVal, "MT TUV");
226
+ const convertedPrevVal = convertValue(prevVal, "MT TUV");
227
+
228
+ const diff = convertedCurrentVal - convertedPrevVal;
229
+
230
+ if (!isNaN(diff) && diff !== 0) {
231
+ sumOfDiffs += Math.abs(diff);
232
+ }
233
+ }
234
+
235
+ // If we found any non-zero difference, meters are being written off
236
+ if (sumOfDiffs > 0) {
237
+ return true;
238
+ }
239
+ }
240
+
241
+ // Meters exist but no differences found (not being written off yet)
242
+ return false;
243
+ };
244
+
245
+ module.exports = { computeEfficiency, areMetersBeingWrittenOff };
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "heatingpro-efficiency-package",
3
+ "version": "1.0.8",
4
+ "description": "Utility functions for MonitoringPro efficiency calculations with flexible unit support (GJ, kWh, MWh)",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "jest",
8
+ "test:watch": "jest --watch",
9
+ "test:coverage": "jest --coverage"
10
+ },
11
+ "keywords": [
12
+ "monitoring",
13
+ "efficiency",
14
+ "energy",
15
+ "utilities",
16
+ "gj",
17
+ "kwh",
18
+ "mwh",
19
+ "gas",
20
+ "heat"
21
+ ],
22
+ "author": "",
23
+ "license": "ISC",
24
+ "files": [
25
+ "index.js",
26
+ "utils/",
27
+ "README.md"
28
+ ],
29
+ "devDependencies": {
30
+ "jest": "^29.7.0"
31
+ }
32
+ }
package/utils/index.js ADDED
@@ -0,0 +1,31 @@
1
+ const toFloatWithDot = (value) => {
2
+ if (typeof value === "string") {
3
+ value = value.replace(",", ".");
4
+ }
5
+ return parseFloat(value);
6
+ };
7
+
8
+ const hasPropertiesAndNotDash = (a, b, key) =>
9
+ a.hasOwnProperty(key) &&
10
+ b.hasOwnProperty(key) &&
11
+ a[key] !== "-" &&
12
+ b[key] !== "-";
13
+
14
+ const convertHeatToKwh = (value, fromUnit) => {
15
+ const unit = fromUnit.toLowerCase();
16
+
17
+ switch (unit) {
18
+ case "gj":
19
+ return value * 277.778; // 1 GJ = 277.778 kWh
20
+ case "kwh":
21
+ return value; // Already in kWh
22
+ case "mwh":
23
+ return value * 1000; // 1 MWh = 1000 kWh
24
+ default:
25
+ throw new Error(
26
+ `Unsupported heat unit: ${fromUnit}. Supported units: GJ, kWh, MWh`
27
+ );
28
+ }
29
+ };
30
+
31
+ module.exports = { toFloatWithDot, hasPropertiesAndNotDash, convertHeatToKwh };