node-red-contrib-prib-functions 0.20.4 → 0.22.0

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 CHANGED
@@ -4,22 +4,22 @@
4
4
 
5
5
  * Data Analysis - statistical metrics that has real time option
6
6
  * Matrix
7
- * Transform
8
- * Test
7
+ * Transform
8
+ * Test
9
9
  * Load Injector
10
- * Monitor Flow
11
- * append
12
- * Spawn Process
13
- * Host Available
14
- * node.js os metrics
15
- * Levenshtein Distance
10
+ * Monitor Flow
11
+ * append
12
+ * Spawn Process
13
+ * Host Available
14
+ * node.js os metrics
15
+ * Levenshtein Distance
16
16
 
17
17
  ------------------------------------------------------------
18
18
 
19
19
  ## Data Analysis
20
20
 
21
21
  Real time metrics which are recalculated on single of data point and posted in msg.result.
22
- Key and value can be selected from msg.payload.
22
+ Key and value can be selected from msg.payload. Includes lag (seasonal) along with delta(defference).
23
23
  Sending message with topic"@stats" places message with all stats on second port.
24
24
  If realtime metrics then a third port is shown where the message is sent if it is an outlier
25
25
  being outside 3 standard deviations from mean. This can be changed to median and number of deviations.
@@ -27,10 +27,11 @@ being outside 3 standard deviations from mean. This can be changed to median and
27
27
  ![Data Analysis Realtime](documentation/DataAnalysisRealtime.JPG "Data Analysis Realtime")
28
28
  ![Data Analysis Pearson R](documentation/DataAnalysisPearsonR.JPG "Data Analysis Pearson R")
29
29
 
30
-
31
30
  A set of data analysis functions that can be run over an array of data
32
31
 
33
32
  Single value metrics:
33
+ * autocorrelation
34
+ * Autocovariance
34
35
  * Average/Mean
35
36
  * Maximum
36
37
  * Median
@@ -78,7 +79,7 @@ example:
78
79
 
79
80
  Define a matrix and perform various functions
80
81
 
81
- * Define / Define Empty / Create / Create Like/ clone"
82
+ * Define / Define Empty / Create / Create Like/ clone
82
83
  * Add / Add Row to Row / Add to Cell / Add Row / Subtract Cell
83
84
  * Multiple / Multiple Cell / Divide Cell / Divide Row
84
85
  * Transpose
@@ -110,48 +111,48 @@ Messages generates a message for each row or record.
110
111
 
111
112
  Transformations:
112
113
 
113
- * Array to CSV
114
- * Array to HTML
115
- * Array to ISO8385
116
- * Array to Messages
117
- * Array to xlsx / xlsx object (excel uses [xlsx][7])
118
- * AVRO to JSON (uses [avsc][6])
114
+ * Array to CSV
115
+ * Array to HTML
116
+ * Array to ISO8385
117
+ * Array to Messages
118
+ * Array to xlsx / xlsx object (excel uses [xlsx][7])
119
+ * AVRO to JSON (uses [avsc][6])
119
120
  * Buffer to comprossed
120
- * Confluence to JSON
121
+ * Confluence to JSON
121
122
  * Compressed to Buffer
122
123
  * Compressed to String
123
124
  * COmpressed to JSON
124
- * CSV to Array
125
- * CSV to HTML
126
- * CSV to Messages
127
- * CSVWithHeader to Array
128
- * CSVWithHeader to HTML
129
- * CSVWithHeader to JSON
130
- * ISO8385 to Array
131
- * ISO8385 to JSON
132
- * JSON to Array
133
- * JSON to Confluence
134
- * JSON to CSV
135
- * JSON to AVRO (uses [avsc][6])
136
- * JSON to ISO8385
137
- * JSON to Messages
138
- * JSON to String
139
- * JSON to xlsx / xlsx object (excel uses [xlsx][7])
140
- * JSON to XML (uses [fast-xml-parser][4])
141
- * String to JSON
142
- * path to Basename
143
- * path to Dirname
144
- * path to Extname
145
- * path to Format
146
- * path to Is Absolute
147
- * path to Join
148
- * path to Parse
149
- * path to Normalize
150
- * path to Resolve
151
- * snappy compress (uses [snappy][5], must install separately)
152
- * snappy uncompress (uses [snappy][5], must install separately)
153
- * xlsx / xlsx object to array/JSON (excel uses [xlsx][7])
154
- * XML to JSON (uses [fast-xml-parser][4])
125
+ * CSV to Array
126
+ * CSV to HTML
127
+ * CSV to Messages
128
+ * CSVWithHeader to Array
129
+ * CSVWithHeader to HTML
130
+ * CSVWithHeader to JSON
131
+ * ISO8385 to Array
132
+ * ISO8385 to JSON
133
+ * JSON to Array
134
+ * JSON to Confluence
135
+ * JSON to CSV
136
+ * JSON to AVRO (uses [avsc][6])
137
+ * JSON to ISO8385
138
+ * JSON to Messages
139
+ * JSON to String
140
+ * JSON to xlsx / xlsx object (excel uses [xlsx][7])
141
+ * JSON to XML (uses [fast-xml-parser][4])
142
+ * String to JSON
143
+ * path to Basename
144
+ * path to Dirname
145
+ * path to Extname
146
+ * path to Format
147
+ * path to Is Absolute
148
+ * path to Join
149
+ * path to Parse
150
+ * path to Normalize
151
+ * path to Resolve
152
+ * snappy compress (uses [snappy][5], must install separately)
153
+ * snappy uncompress (uses [snappy][5], must install separately)
154
+ * xlsx / xlsx object to array/JSON (excel uses [xlsx][7])
155
+ * XML to JSON (uses [fast-xml-parser][4])
155
156
 
156
157
  Note, snappy needs to be installed separately as can have issues with auto install as build binaries.
157
158
 
@@ -219,7 +220,6 @@ Test example:
219
220
 
220
221
  ![Load Injector example](documentation/LoadInjectorTest.JPG "Load Injector example")
221
222
 
222
-
223
223
  ------------------------------------------------------------
224
224
 
225
225
  ## Monitor Flow
@@ -235,34 +235,29 @@ Test example:
235
235
 
236
236
  ------------------------------------------------------------
237
237
 
238
- ## Spawn Process
238
+ ## Host Available
239
239
 
240
- Spawn process as per node.js manual with ability to set working directory, environment variables
241
- and argument passed to process. STDOUT and STDERR are sent as individual messages.
242
- RC port is sent a message on closure.
243
- Takes in messages that starts a process with ability to add environment values.
244
- Message can be sent to kill the process.
240
+ Test if host is available sending msg to up or down port so action can be taken.
241
+ Message only sent on state change or if message is sent which doesn't have topic refreshHostAvailable.
242
+ This topic forces a check rather than time check which can be set.
245
243
 
246
- ![Spawn Process](documentation/SpawnProcess.JPG "Spawn Process")
244
+ ![Host Available](documentation/hostAvailable.JPG "Host Available")
247
245
 
248
246
  Test example:
249
247
 
250
- ![Spawn Process example](documentation/SpawnProcessTest.JPG "Spawn Process example")
251
-
248
+ ![Host Available example](documentation/hostAvailableTest.JPG "Host Available example")
252
249
 
253
250
  ------------------------------------------------------------
254
251
 
255
- ## Host Available
252
+ ## Monitor System
256
253
 
257
- Test if host is available sending msg to up or down port so action can be taken.
258
- Message only sent on state change or if message is sent which doesn't have topic refreshHostAvailable.
259
- This topic forces a check rather than time check which can be set.
254
+ System monitoring metrics
260
255
 
261
- ![Host Available](documentation/hostAvailable.JPG "Host Available")
256
+ ![Monitor System](documentation/monitorSystem.JPG "Monitor System")
262
257
 
263
258
  Test example:
264
259
 
265
- ![Host Available example](documentation/hostAvailableTest.JPG "Host Available example")
260
+ ![Monitor System example](documentation/monitorSystemTest.JPG "Monitor System example")
266
261
 
267
262
  ------------------------------------------------------------
268
263
 
@@ -278,6 +273,22 @@ Test example:
278
273
 
279
274
  ------------------------------------------------------------
280
275
 
276
+ ## Spawn Process
277
+
278
+ Spawn process as per node.js manual with ability to set working directory, environment variables
279
+ and argument passed to process. STDOUT and STDERR are sent as individual messages.
280
+ RC port is sent a message on closure.
281
+ Takes in messages that starts a process with ability to add environment values.
282
+ Message can be sent to kill the process.
283
+
284
+ ![Spawn Process](documentation/SpawnProcess.JPG "Spawn Process")
285
+
286
+ Test example:
287
+
288
+ ![Spawn Process example](documentation/SpawnProcessTest.JPG "Spawn Process example")
289
+
290
+ ------------------------------------------------------------
291
+
281
292
  # Install
282
293
 
283
294
  Run the following command in the root directory of your Node-RED install
@@ -292,15 +303,18 @@ Test/example flow in test/generalTest.json
292
303
 
293
304
  # Version
294
305
 
306
+ 0.22.0 Add autocovariance + autocorealationship to real time data analystics, improves test
307
+
308
+ 0.21.0 Add lag/seasonal to real time data analystics
309
+
310
+ 0.20.3 Add difference + monitor system
311
+
295
312
  0.19.0 Improve load injector, fix bug in test comparing buffers, add compression tranforms
296
313
 
297
314
  0.18.0 Add matrix node
298
315
 
299
316
  0.17.0 Add finished wire to load injector
300
317
 
301
- 0.16.0 fix data analysis variance and stddev, add sample, add tests
302
-
303
-
304
318
 
305
319
  # Author
306
320
 
@@ -0,0 +1,13 @@
1
+ const arrayTypesForEach=require("./arrayTypesForEach.js")
2
+ require("./arraySum.js")
3
+
4
+ arrayTypesForEach(object=>{
5
+ if(object.prototype.average==null)
6
+ Object.defineProperty(object.prototype, "average", {
7
+ value(i=0,end=this.length-1) {
8
+ return this.sum(i,end)/this.length;
9
+ },
10
+ writable: true,
11
+ configurable: true
12
+ });
13
+ })
@@ -1,3 +1,4 @@
1
+ /*
1
2
  [Array,Float32Array,Float64Array].forEach(object=>{
2
3
  if(object.prototype.sum==null)
3
4
  Object.defineProperty(object.prototype, "sum", {
@@ -8,4 +9,17 @@
8
9
  writable: true,
9
10
  configurable: true
10
11
  });
12
+ })
13
+ */
14
+ const arrayTypesForEach=require("./arrayTypesForEach.js")
15
+ arrayTypesForEach(object=>{
16
+ if(object.prototype.sum==null)
17
+ Object.defineProperty(object.prototype, "sum", {
18
+ value(i=0,end=this.length-1,sum=0) {
19
+ for(;i<=end;i++) sum+=this[i];
20
+ return sum;
21
+ },
22
+ writable: true,
23
+ configurable: true
24
+ });
11
25
  })
@@ -0,0 +1,5 @@
1
+ const typedArrays= {Array:Array,Int8Array:Int8Array,Uint8Array:Uint8Array,Uint8ClampedArray:Uint8ClampedArray,Int16Array:Int16Array,Uint16Array:Uint16Array,
2
+ Int32Array:Int32Array,Uint32Array:Uint32Array,Float32Array:Float32Array,
3
+ Float64Array:Float64Array,BigInt64Array:BigInt64Array,BigUint64Array:BigUint64Array}
4
+
5
+ module.exports=(call)=>Object.keys(typedArrays).map(t=>typedArrays[t]).forEach(type=>call(type))
@@ -0,0 +1,29 @@
1
+
2
+ require("./arrayAverage.js");
3
+ const arrayTypesForEach=require("./arrayTypesForEach.js")
4
+ arrayTypesForEach(object=>{
5
+ if(object.prototype.autocovariance==null)
6
+ Object.defineProperty(object.prototype, "autocovariance", {
7
+ value(lag,avg=this.average()) {
8
+ let autoCov = 0
9
+ const vectorSize=this.length
10
+ const vectorSizeLagged=vectorSize-lag
11
+ for(let i=0; i<vectorSizeLagged; i++) {
12
+ autoCov += ((this[i+lag])-avg)*(this[i]-avg)
13
+ }
14
+ return (1/(vectorSize-1))*autoCov
15
+ },
16
+ writable: true,
17
+ configurable: true
18
+ })
19
+ })
20
+ arrayTypesForEach(object=>{
21
+ if(object.prototype.autocorrelation==null)
22
+ Object.defineProperty(object.prototype, "autocorrelation", {
23
+ value(lag,avg=this.average()) {
24
+ return this.autocovariance(lag, avg) / this.autocovariance(0, avg)
25
+ },
26
+ writable: true,
27
+ configurable: true
28
+ })
29
+ })
@@ -11,7 +11,7 @@
11
11
  outputs: {value:(["realtime","realtimePredict"].includes(this.action)?3:2),required:true},
12
12
  outliersBase: {value:"avg",required:false},
13
13
  outliersStdDevs: {value:"3",required:false},
14
- term: {value:"10",required:false},
14
+ term: {value:10,required:false},
15
15
  keyProperty: {value:"msg.topic",required:false},
16
16
  dataProperty: {value:"msg.payload",required:false},
17
17
  dataProperties: {value:["msg.payload[0]","msg.payload[1]"],required:false}
@@ -28,6 +28,7 @@
28
28
  },
29
29
  oneditprepare: function() {
30
30
  const node=this;
31
+ node.lag??=1
31
32
  $("#node-input-keyProperty").change(function() {
32
33
  if( [null,""].includes(node.keyProperty) ) {
33
34
  $(this).val("msg.topic");
@@ -46,7 +47,7 @@
46
47
  }
47
48
  });
48
49
  $("#node-input-action").change(function() {
49
- if(["differenceSeasonal","differenceSeasonalSecondOrder"].includes( $(this).val() )) {
50
+ if(["autocovariance","autocorrelation","differenceSeasonal","differenceSeasonalSecondOrder","realtime","realtimePredict"].includes( $(this).val() )) {
50
51
  $(".form-row-http-in-lag").show();
51
52
  } else {
52
53
  $(".form-row-http-in-lag").hide();
@@ -69,10 +70,17 @@
69
70
  if(node.term>1) $(this).val(1);
70
71
  });
71
72
  } else {
73
+ $("#node-input-lag").attr({min:1,max:1000,step:1});
74
+ $("#node-input-lag").change(function() {
75
+ const v=$(this).val();
76
+ if(v<1) $(this).val(1);
77
+ if(v>1000) $(this).val(1000);
78
+ });
72
79
  $("#node-input-term").attr({min:1,max:100,step:1});
73
80
  $("#node-input-term").change(function() {
74
- if(node.term<1) $(this).val(1);
75
- if(node.term>100) $(this).val(100);
81
+ const v=$(this).val()
82
+ if(v<1) $(this).val(1);
83
+ if(v>100) $(this).val(100);
76
84
  });
77
85
  }
78
86
  if(["realtime","realtimePredict"].includes( $(this).val() )) {
@@ -94,8 +102,8 @@
94
102
  $(".node-input-dataProperties-container-row").hide();
95
103
  $(".form-row-http-in-dataProperty").show();
96
104
  }
97
- $("#node-input-term").change();
98
105
  $("#node-input-lag").change();
106
+ $("#node-input-term").change();
99
107
  });
100
108
 
101
109
  node.dataProperties.forEach((r)=>addDataProperty(r));
@@ -150,6 +158,8 @@
150
158
  <div class="form-row">
151
159
  <label for="node-input-action"><i class="fa fa-list-ul"></i> Method</label>
152
160
  <select id="node-input-action" placeholder="action">
161
+ <option value="autocovariance">Autocovariance</option>
162
+ <option value="autocorrelation">Autocorrelation</option>
153
163
  <option value="avg">Average</option>
154
164
  <option value="covariance">Covariance</option>
155
165
  <option value="corelationship">Corelationship</option>
@@ -248,12 +258,18 @@ Data is not persisted so metrics start from zero sample set on node recycle.
248
258
  <p>
249
259
  If real-time stats then a message can send directive instruction in topic:
250
260
  <dl>
251
- <dt>"@predict <key>"<\dt><dd></dd>Send send predictions to second port for selected key.
252
- <dt>"@stats"<\dt><dd></dd>send all stored metrics and retained datapoints to second port.
253
- <dt>"@stats reset"<\dt><dd>Reset all stats and "@stats reset <key>" will reset a particular data point.</dd>
254
- <dt>"@stats set"<\dt><dd>Set stats with ith msg.payload and "@stats set <a data point>" will set a particular data point with msg.payload.</dd>
261
+ <dt>"@predict <key>"</dt><dd></dd>Send send predictions to second port for selected key.
262
+ <dt>"@stats"</dt><dd></dd>send all stored metrics and retained datapoints to second port.
263
+ <dt>"@stats reset"</dt><dd>Reset all stats and "@stats reset <key>" will reset a particular data point.</dd>
264
+ <dt>"@stats set"</dt><dd>Set stats with ith msg.payload and "@stats set &lt;a data point&gt;" will set a particular data point with msg.payload.</dd>
255
265
  </dl>
256
266
  </p>
267
+ <dl>
268
+ <dt>Lag</dt><dd>If greater that 1 generates seasonal difference with degree lag</dd>
269
+ <dt>Term</dt><dd>The depth of moving Average</dd>
270
+ </dl>
271
+ <p>
272
+ </p>
257
273
  <p>
258
274
  Outliers are not within:
259
275
  <ul>
@@ -1,6 +1,7 @@
1
1
  const logger = new (require("node-red-contrib-logger"))("Data Analysis");
2
2
  logger.sendInfo("Copyright 2020 Jaroslav Peter Prib");
3
3
 
4
+ require("./autocorrelation.js");
4
5
  require("./arrayLast");
5
6
  require("./arrayDifference")
6
7
  require("./arrayDifferenceSeasonal")
@@ -69,10 +70,10 @@ EMA.prototype.sample=function(value) {
69
70
  return this;
70
71
  }
71
72
 
72
- function setDataPoint(value,term,node,dp) {
73
- if(logger.active) logger.send({label:"setDataPoint",value:value,term,dp});
74
- if(!dp.values) {
75
- Object.assign(dp,{
73
+ function setDataPoint(value,term,node,dataPoint) {
74
+ if(logger.active) logger.send({label:"setDataPoint",value:value,term,dataPoint});
75
+ if(!dataPoint.values) {
76
+ Object.assign(dataPoint,{
76
77
  values:[],
77
78
  avg:0,
78
79
  count:0,
@@ -83,43 +84,44 @@ function setDataPoint(value,term,node,dp) {
83
84
  sum:0,
84
85
  sumSquared:0,
85
86
  sumCubed:0,
86
- term:term,
87
+ term:term??node.term,
87
88
  weightedMovingSum:0,
88
89
  exponentialWeightedMoving:[new EMA(0.25),new EMA(0.5),new EMA(0.75)]
89
90
  });
90
- }
91
- ;
92
- const count=++dp.count,values=dp.values;
91
+ };
92
+ const count=++dataPoint.count,values=dataPoint.values;
93
93
  values.push(value);
94
- dp.isMaxSize=(values.length>dp.term);
95
- dp.removedValue=(dp.isMaxSize?values.shift():0);
96
- const removedValue=dp.removedValue;
97
- dp.max=Math.max(dp.max||value,value);
98
- dp.min=Math.min(dp.min||value,value);
99
- dp.range=dp.max-dp.min;
100
- dp.sum+=value;
101
- dp.sumSquared+=Math.pow(value,2);
102
- dp.sumCubed+=Math.pow(value,3);
103
- dp.movingSum+=value-removedValue;
104
- dp.movingSumSquared+=Math.pow(value,2)-Math.pow(removedValue,2);
105
- dp.movingSumCubed+=Math.pow(value,3)-Math.pow(removedValue,3);
106
- dp.avg=dp.sum/count;
107
- const avg=dp.avg;
108
- dp.normalised=dp.range ? (value-avg)/dp.range : 0;
109
- dp.movingAvg=dp.movingSum/values.length;
110
- dp.variance=dp.sumSquared/count - Math.pow(avg,2);
111
- dp.stdDev=Math.sqrt(dp.variance);
112
- dp.movingVariance=dp.movingSumSquared/values.length - Math.pow(dp.movingAvg,2);
113
- dp.movingStdDev=Math.sqrt(dp.movingVariance);
114
- dp.median=functions.median(values);
115
- dp.standardized=( (value-avg)/dp.stdDev )||0;
116
- dp.movingStandardized=( (value-dp.movingAvg)/dp.movingStdDev )||0;
117
- dp.skewness=(dp.sumCubed-3*avg*dp.variance-Math.pow(avg,3))/dp.variance*dp.stdDev;
118
- dp.movingSkewness=(dp.movingSumCubed-3*dp.movingAvg*dp.movingVariance-Math.pow(dp.movingAvg,3))/dp.movingVariance*dp.stdDev;
119
- dp.outlier=node.outliersFunction(node,dp,value);
120
- dp.weightedMovingSum+=count*value;
121
- dp.weightedMovingAvg=(dp.weightedMovingAvg*2/count)/(count+1);
122
- dp.exponentialWeightedMoving.forEach(c=>c.sample(value));
94
+ const movingTerm=Math.min(values.length,dataPoint.term)
95
+ dataPoint.isMaxSize=(values.length>dataPoint.maxSize);
96
+ dataPoint.removedMovingValue=(dataPoint.isMaxSize?values[values.length-dataPoint.term]:0);
97
+ dataPoint.removedValue=(dataPoint.isMaxSize?values.shift():0);
98
+ const removedMovingValue=dataPoint.removedMovingValue;
99
+ dataPoint.max=Math.max(dataPoint.max||value,value);
100
+ dataPoint.min=Math.min(dataPoint.min||value,value);
101
+ dataPoint.range=dataPoint.max-dataPoint.min;
102
+ dataPoint.sum+=value;
103
+ dataPoint.sumSquared+=Math.pow(value,2);
104
+ dataPoint.sumCubed+=Math.pow(value,3);
105
+ dataPoint.movingSum+=value-removedMovingValue;
106
+ dataPoint.movingSumSquared+=Math.pow(value,2)-Math.pow(removedMovingValue,2);
107
+ dataPoint.movingSumCubed+=Math.pow(value,3)-Math.pow(removedMovingValue,3);
108
+ dataPoint.avg=dataPoint.sum/count;
109
+ const avg=dataPoint.avg;
110
+ dataPoint.normalised=dataPoint.range ? (value-avg)/dataPoint.range : 0;
111
+ dataPoint.movingAvg=dataPoint.movingSum/movingTerm;
112
+ dataPoint.variance=dataPoint.sumSquared/count - Math.pow(avg,2);
113
+ dataPoint.stdDev=Math.sqrt(dataPoint.variance);
114
+ dataPoint.movingVariance=dataPoint.movingSumSquared/movingTerm - Math.pow(dataPoint.movingAvg,2);
115
+ dataPoint.movingStdDev=Math.sqrt(dataPoint.movingVariance);
116
+ dataPoint.median=functions.median(values);
117
+ dataPoint.standardized=( (value-avg)/dataPoint.stdDev )||0;
118
+ dataPoint.movingStandardized=( (value-dataPoint.movingAvg)/dataPoint.movingStdDev )||0;
119
+ dataPoint.skewness=(dataPoint.sumCubed-3*avg*dataPoint.variance-Math.pow(avg,3))/dataPoint.variance*dataPoint.stdDev;
120
+ dataPoint.movingSkewness=(dataPoint.movingSumCubed-3*dataPoint.movingAvg*dataPoint.movingVariance-Math.pow(dataPoint.movingAvg,3))/dataPoint.movingVariance*dataPoint.stdDev;
121
+ dataPoint.outlier=node.outliersFunction(node,dataPoint,value);
122
+ dataPoint.weightedMovingSum+=count*value;
123
+ dataPoint.weightedMovingAvg=(dataPoint.weightedMovingAvg*2/count)/(count+1);
124
+ dataPoint.exponentialWeightedMoving.forEach(c=>c.sample(value));
123
125
  }
124
126
  function getColumns(node) {
125
127
  if(node.columns) {
@@ -128,6 +130,8 @@ function getColumns(node) {
128
130
  }
129
131
  }
130
132
  functions={
133
+ autocovariance:(d,term,node)=>d.autocovariance(node.lag),
134
+ autocorrelation:(d,term,node)=>d.autocorrelation(node.lag),
131
135
  avg:(d)=>functions.sum(d)/d.length,
132
136
  arrayMax:(d)=>{ // not tested
133
137
  let max=[],indices
@@ -223,9 +227,9 @@ functions={
223
227
  }
224
228
  }
225
229
  node.samples++;
226
- for(let v,dp,i=0; i<node.dataProperties.length; i++) {
227
- v=d[i];
228
- dp=node.dataPoints[i];
230
+ for(let i=0; i<node.dataProperties.length; i++) {
231
+ const v=d[i];
232
+ const dp=node.dataPoints[i];
229
233
  dp.sum+=v;
230
234
  dp.sumSquared+=v*v;
231
235
  for(let j=i+1; j<node.dataProperties.length; j++) {
@@ -280,10 +284,23 @@ functions={
280
284
  }
281
285
  setDataPoint(d.value,term,node,dp);
282
286
  if(dp.delta) {
283
- setDataPoint(d.value-dp.values[dp.values.length-2],term,node,dp.delta);
287
+ if(dp.values.length>1)
288
+ setDataPoint(d.value-dp.values[dp.values.length-2],term,node,dp.delta);
284
289
  } else {
285
290
  dp.delta={};
286
291
  }
292
+ if(node.lag>1) {
293
+ const vectorSize=dp.values.length
294
+ if(dp.lag) {
295
+ if(node.lag<=vectorSize){
296
+ setDataPoint(d.value-dp.values[vectorSize-node.lag],term,node,dp.lag)
297
+ const values=dp.lag.values
298
+ if(values.length>1) setDataPoint(values[values.length-1]-values[values.length-2],term,node,dp.lag.delta)
299
+ }
300
+ } else {
301
+ dp.lag={delta:{}}
302
+ }
303
+ }
287
304
  return dp;
288
305
  },
289
306
  sampleVariance:(d)=>{
@@ -310,11 +327,15 @@ module.exports = function (RED) {
310
327
  function dataAnalysisNode(n) {
311
328
  RED.nodes.createNode(this, n);
312
329
  const node=Object.assign(this,
313
- {outliersStdDevs:3,crossNormalisedDeltas:crossNormalisedDeltas.bind(this)},
330
+ {outliersStdDevs:3,crossNormalisedDeltas:crossNormalisedDeltas.bind(this),lag:1,term:3},
314
331
  n,
315
332
  {maxErrorDisplay:10,dataPoint:{}}
316
333
  );
317
334
  try{
335
+ node.lag=Number(node.lag)
336
+ if(Number(node.term)==NaN)throw Error("term not a number, value:"+JSON.stringify(node.term))
337
+ node.term=Number(node.term)
338
+ node.maxSize=Math.max(node.term,node.lag)
318
339
  if(functions.hasOwnProperty(node.action)) {
319
340
  node.actionfunction=functions[node.action];
320
341
  } else {
@@ -349,6 +370,7 @@ module.exports = function (RED) {
349
370
  node.getData=eval(node.getDatafunction);
350
371
  node.status({fill:"green",shape:"ring",text:"Ready"});
351
372
  } catch(ex) {
373
+ if(logger.active) logger.send({label:"initialise error",action:node.action,message:ex.message,stack:ex.stack});
352
374
  logger.send({label:"initialise error",node:n});
353
375
  node.error(ex);
354
376
  node.status({fill:"red",shape:"ring",text:"Invalid setup "+ex.message});
@@ -388,6 +410,7 @@ module.exports = function (RED) {
388
410
  throw Error("unknown");
389
411
  }
390
412
  } catch(ex) {
413
+ if(logger.active) logger.send({label:"error input",action:node.action,message:ex.message,stack:ex.stack});
391
414
  node.error(msg.topic+" failed "+ex.message);
392
415
  }
393
416
  return;
@@ -404,10 +427,10 @@ module.exports = function (RED) {
404
427
  break;
405
428
  }
406
429
  } catch(ex) {
407
- if(logger.active) logger.send({label:"error input",action:node.action,message:ex.message,stack:ex.stack});
408
430
  msg.error=ex.message;
409
431
  if(node.maxErrorDisplay) {
410
432
  --node.maxErrorDisplay;
433
+ logger.send({label:"error input",action:node.action,message:ex.message,stack:ex.stack});
411
434
  if(node.action=="realtime") {
412
435
  node.error(node.action+" error: "+ex.message);
413
436
  } else {
Binary file
Binary file