vaporous 0.0.2

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/Vaporous.js ADDED
@@ -0,0 +1,512 @@
1
+ #!/usr/bin/env node
2
+
3
+ const dayjs = require('dayjs');
4
+ const fs = require('fs');
5
+ const split2 = require('split2');
6
+
7
+ const By = require('./types/By');
8
+ const Aggregation = require('./types/Aggregation');
9
+ const Window = require('./types/Window')
10
+ const path = require('path')
11
+
12
+ const styles = fs.readFileSync(__dirname + '/styles.css')
13
+
14
+ // These globals allow us to write functions from the HTML page directly without needing to stringify
15
+ class google { }
16
+ const document = {}
17
+
18
+ const keyFromEvent = (event, bys) => bys.map(i => event[i.bySplit]).join('|')
19
+
20
+ const _sort = (order, data, ...keys) => {
21
+ return data.sort((a, b) => {
22
+ let directive = 0;
23
+ keys.some(key => {
24
+ const type = typeof a[key];
25
+
26
+ if (type === 'number') {
27
+ directive = order === 'asc' ? a[key] - b[key] : b[key] - a[key];
28
+ } else if (type === 'string') {
29
+ directive = order === 'asc' ? a[key].localeCompare(b[key]) : b[key].localeCompare(a[key]);
30
+ }
31
+
32
+ if (directive !== 0) return true;
33
+ })
34
+
35
+ return directive;
36
+ })
37
+ }
38
+
39
+ class Vaporous {
40
+
41
+ constructor() {
42
+ this.events = [];
43
+ this.visualisations = [];
44
+ this.visualisationData = []
45
+ this.graphFlags = []
46
+ this.tabs = []
47
+
48
+ this.savedMethods = {}
49
+ this.checkpoints = {}
50
+ }
51
+
52
+ method(operation, name, method) {
53
+ const operations = {
54
+ create: () => {
55
+ this.savedMethods[name] = method
56
+ },
57
+ retrieve: () => {
58
+ this.savedMethods[name](this)
59
+ },
60
+ delete: () => {
61
+ delete this.savedMethods[name]
62
+ }
63
+ }
64
+
65
+
66
+ operations[operation]()
67
+ return this
68
+ }
69
+
70
+ filter(...args) {
71
+ this.events = this.events.filter(...args)
72
+ return this
73
+ }
74
+
75
+ append(entities) {
76
+ this.events = this.events.concat(entities)
77
+ return this;
78
+ }
79
+
80
+ eval(modifier) {
81
+ this.events.forEach(event => {
82
+ const vals = modifier(event)
83
+ if (vals) Object.assign(event, vals)
84
+ })
85
+ return this;
86
+ }
87
+
88
+ table(modifier) {
89
+ this.events = this.events.map(event => {
90
+ const vals = modifier(event)
91
+ return vals;
92
+ })
93
+ return this;
94
+ }
95
+
96
+ rename(...entities) {
97
+ this.events.forEach(event => {
98
+ entities.forEach(([from, to]) => {
99
+ event[to] = event[from]
100
+ delete event[from]
101
+ })
102
+ })
103
+ return this;
104
+ }
105
+
106
+ parseTime(value, customFormat) {
107
+ this.events.forEach(event => {
108
+ event[value] = dayjs(event[value], customFormat).valueOf()
109
+ })
110
+ return this;
111
+ }
112
+
113
+ bin(value, span) {
114
+ this.events.forEach(event => {
115
+ event[value] = Math.floor(event[value] / span) * span
116
+ })
117
+ return this;
118
+ }
119
+
120
+ fileScan(directory) {
121
+ const items = fs.readdirSync(directory)
122
+ this.events = items.map(item => {
123
+ return {
124
+ _fileInput: path.resolve(directory, item)
125
+ }
126
+ })
127
+ return this;
128
+ }
129
+
130
+ async fileLoad(delim, parser) {
131
+ const tasks = this.events.map(obj => {
132
+ const content = []
133
+
134
+ return new Promise(resolve => {
135
+ fs.createReadStream(obj._fileInput)
136
+ .pipe(split2(delim))
137
+ .on('data', line => {
138
+ const event = parser(line)
139
+ if (event !== null) content.push(event)
140
+ })
141
+ .on('end', () => {
142
+ obj._raw = content;
143
+ resolve(this)
144
+ })
145
+ })
146
+ })
147
+
148
+ await Promise.all(tasks)
149
+ return this;
150
+ }
151
+
152
+ output() {
153
+ console.log(this.events)
154
+ return this;
155
+ }
156
+
157
+ flatten() {
158
+ const arraySize = this.events.reduce((acc, obj) => acc + obj._raw.length, 0)
159
+ let flattened = new Array(arraySize)
160
+ let i = 0
161
+
162
+ this.events.forEach(obj => {
163
+ const raws = obj._raw
164
+ delete obj._raw
165
+
166
+ raws.forEach(event => {
167
+ flattened[i++] = {
168
+ ...obj,
169
+ _raw: event,
170
+ }
171
+ })
172
+
173
+ })
174
+ this.events = flattened;
175
+ return this;
176
+ }
177
+
178
+ _stats(args, events) {
179
+ const by = args.filter(arg => arg instanceof By)
180
+ const aggregations = args.filter(arg => arg instanceof Aggregation);
181
+ const targetFields = [... new Set(aggregations.map(i => i.field))]
182
+
183
+ const map = {}
184
+
185
+ events.forEach(item => {
186
+ const key = keyFromEvent(item, by)
187
+
188
+ if (!map[key]) {
189
+ map[key] = {
190
+ _statsRaw: {},
191
+ }
192
+
193
+ // Add key fields
194
+ by.forEach(i => {
195
+ map[key][i.bySplit] = item[i.bySplit]
196
+ })
197
+ }
198
+
199
+ targetFields.forEach(field => {
200
+ if (!map[key]._statsRaw[field]) map[key]._statsRaw[field] = [];
201
+ const _values = map[key]._statsRaw[field];
202
+ _values.push(item[field])
203
+ })
204
+ })
205
+
206
+ const arr = Object.keys(map).map(key => {
207
+ const result = map[key]
208
+
209
+ aggregations.forEach(aggregation => {
210
+ if (aggregation.sortable) map[key]._statsRaw[aggregation.field].sort((a, b) => a - b)
211
+
212
+ const aggregationField = aggregation.outputField
213
+ result[aggregationField] = aggregation.calculate(map[key])
214
+ })
215
+
216
+ delete map[key]._statsRaw
217
+ return map[key]
218
+ })
219
+
220
+ return { arr, map, by, aggregations }
221
+ }
222
+
223
+ stats(...args) {
224
+ this.events = this._stats(args, this.events).arr
225
+ return this;
226
+ }
227
+
228
+ eventstats(...args) {
229
+ const stats = this._stats(args, this.events)
230
+
231
+ this.events.forEach(event => {
232
+ const key = keyFromEvent(event, stats.by)
233
+
234
+ Object.assign(event, stats.map[key])
235
+ })
236
+
237
+ return this
238
+ }
239
+
240
+ streamstats(...args) {
241
+ const window = args.filter(i => i instanceof Window)
242
+ const by = args.filter(i => i instanceof By)
243
+
244
+ // Perform some validation
245
+ if (window.length > 1) throw new Error('Only one window allowed in streamstats')
246
+ if (window.length > 0 && by.length > 0) throw new Error('Window and By not supported together in streamstats')
247
+
248
+ this.events.forEach((event, i) => {
249
+ let start, byKey;
250
+ if (window.length > 0) {
251
+ start = Math.max(i - window[0].size + 1, 0)
252
+ byKey = ""
253
+ } else if (by.length > 0) {
254
+ let backwardIndex = 0
255
+ const thisKey = keyFromEvent(event, by)
256
+ byKey = thisKey
257
+ let keyChange = false
258
+ while (!keyChange) {
259
+ const target = i - backwardIndex
260
+
261
+ if (target < 0) {
262
+ keyChange = true
263
+ break
264
+ }
265
+
266
+ const newKey = keyFromEvent(this.events[target], by)
267
+ if (thisKey !== newKey) {
268
+ keyChange = true
269
+ break
270
+ }
271
+
272
+ backwardIndex++
273
+ }
274
+ start = Math.max(i - backwardIndex + 1, 0)
275
+ }
276
+
277
+ const eventRange = this.events.slice(start, i + 1)
278
+ Object.assign(event, this._stats(args, eventRange).map[byKey])
279
+ })
280
+
281
+ return this;
282
+ }
283
+
284
+ sort(order, ...keys) {
285
+ this._events = _sort(order, this.events, keys)
286
+ return this;
287
+ }
288
+
289
+ assert(funct) {
290
+ const expect = (funct) => { if (!funct) throw new Error('Assertion failed') }
291
+ this.events.forEach((event, i) => {
292
+ funct(event, i, { expect })
293
+ })
294
+ return this;
295
+ }
296
+
297
+ build(name, type, visualisationOptions) {
298
+ const data = JSON.stringify(this.events)
299
+ const lastData = this.visualisationData.at(-1)
300
+
301
+ if (lastData !== data) this.visualisationData.push(data)
302
+ this.visualisations.push([name, type, visualisationOptions, this.visualisationData.length - 1, this.graphFlags[this.graphFlags.length - 1]])
303
+
304
+ if (visualisationOptions.tab && !this.tabs.includes(visualisationOptions.tab)) this.tabs.push(visualisationOptions.tab)
305
+
306
+ return this;
307
+ }
308
+
309
+ checkpoint(operation, name) {
310
+
311
+ const operations = {
312
+ create: () => this.checkpoints[name] = this.events,
313
+ retrieve: () => this.events = this.checkpoints[name],
314
+ delete: () => delete this.checkpoints[name]
315
+ }
316
+
317
+ operations[operation]()
318
+ return this;
319
+ }
320
+
321
+ mvexpand(target) {
322
+ const arr = []
323
+ this.events.forEach(event => {
324
+ if (!event[target]) return arr.push(event)
325
+ event[target].forEach((item) => {
326
+ arr.push({
327
+ ...event,
328
+ [target]: item
329
+ })
330
+ })
331
+ })
332
+
333
+ this.events = arr
334
+ return this;
335
+ }
336
+
337
+ toGraph(x, y, series, trellis, options = {}) {
338
+
339
+ this.stats(
340
+ new Aggregation(y, 'list', y),
341
+ new Aggregation(series, 'list', series),
342
+ new Aggregation(trellis, 'values', 'trellis'),
343
+ new By(x), trellis ? new By(trellis) : null
344
+ )
345
+
346
+ const trellisMap = {}
347
+
348
+ this.table(event => {
349
+ const obj = {
350
+ _time: event[x]
351
+ }
352
+ event[series].forEach((series, i) => obj[series] = event[y][i])
353
+
354
+ if (trellis) {
355
+ const tval = event.trellis[0]
356
+ if (!trellisMap[tval]) trellisMap[tval] = []
357
+ trellisMap[tval].push(obj)
358
+ }
359
+
360
+ return obj
361
+ })
362
+
363
+ const graphFlags = {}
364
+ if (trellis) {
365
+ graphFlags.trellis = true;
366
+ graphFlags.trellisName = Object.keys(trellisMap)
367
+ this.events = Object.keys(trellisMap).map(tval => trellisMap[tval])
368
+ }
369
+
370
+ Object.assign(graphFlags, options)
371
+ this.graphFlags.push(graphFlags)
372
+ return this;
373
+ }
374
+
375
+ render() {
376
+ const classSafe = (name) => name.replace(/[^a-zA-Z0-9]/g, "_")
377
+
378
+ const createElement = (name, type, visualisationOptions, eventData, { trellis, y2, sortX, trellisName = "", y2Type, y1Type, stacked, y1Min, y2Min }) => {
379
+ if (classSafe(visualisationOptions.tab) !== selectedTab) return;
380
+
381
+ eventData = visualisationData[eventData]
382
+ if (!trellis) eventData = [eventData]
383
+ else {
384
+ let pairs = trellisName.map((name, i) => [name, eventData[i]]);
385
+ pairs = pairs.sort((a, b) => a[0].localeCompare(b[0]))
386
+
387
+ // Unzip back into separate arrays
388
+ trellisName = pairs.map(p => p[0]);
389
+ eventData = pairs.map(p => p[1]);
390
+ }
391
+
392
+ eventData.forEach((trellis, i) => {
393
+ const data = new google.visualization.DataTable();
394
+
395
+ const series = {}, axis0 = { targetAxisIndex: 0 }, axis1 = { targetAxisIndex: 1 }
396
+
397
+ if (y1Type) axis0.type = y1Type
398
+ if (y2Type) axis1.type = y2Type
399
+
400
+ // Create columns
401
+ const columns = Object.keys(trellis[0])
402
+ columns.forEach((key, i) => {
403
+ data.addColumn(typeof trellis[0][key], key)
404
+
405
+ if (y2 && i !== 0) {
406
+ let match = false;
407
+ if (y2 instanceof Array) { match = y2.includes(key) }
408
+ else if (y2 instanceof RegExp) { match = y2.test(key) }
409
+
410
+ if (match) series[i - 1] = axis1
411
+ }
412
+
413
+ if (!series[1 - i]) series[i - 1] = axis0
414
+ })
415
+
416
+ let rows = trellis.map(event => {
417
+ return columns.map(key => event[key])
418
+ })
419
+
420
+ rows = _sort(sortX, rows, 0)
421
+
422
+ data.addRows(rows);
423
+
424
+ const columnCount = visualisationOptions.columns || 2
425
+ const thisEntity = document.createElement('div')
426
+ thisEntity.className = "parentHolder"
427
+ thisEntity.style = `flex: 1 0 calc(${100 / columns}% - 6px); max-width: calc(${100 / columnCount}% - 6px);`
428
+
429
+
430
+ const thisGraph = document.createElement('div')
431
+ thisGraph.className = "graphHolder"
432
+ thisEntity.appendChild(thisGraph)
433
+ document.getElementById('content').appendChild(thisEntity)
434
+
435
+ const chartElement = new google.visualization[type](thisGraph)
436
+
437
+ google.visualization.events.addListener(chartElement, 'select', (e) => {
438
+ console.log(chartElement.getSelection()[1], chartElement.getSelection()[0])
439
+ tokens[name] = trellis[chartElement.getSelection()[0].row]
440
+ console.log(tokens[name])
441
+ });
442
+
443
+ const title = trellis ? name + trellisName[i] : name
444
+
445
+ chartElement.draw(data, {
446
+ series, showRowNumber: false, legend: { position: 'bottom' }, title, isStacked: stacked,
447
+ width: document.body.scrollWidth / columnCount - (type === "LineChart" ? 12 : 24),
448
+ animation: { duration: 500, startup: true },
449
+ chartArea: { width: '85%', height: '75%' },
450
+ vAxis: {
451
+ viewWindow: {
452
+ min: y1Min
453
+ }
454
+ }
455
+ })
456
+ })
457
+ }
458
+
459
+ const filePath = './Vaporous_generation.html'
460
+ fs.writeFileSync(filePath, `
461
+ <html>
462
+ <head>
463
+ <style>
464
+ ${styles}
465
+ </style>
466
+ <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
467
+ <script type="text/javascript">
468
+ google.charts.load('current', {'packages':['table', 'corechart']});
469
+ google.charts.setOnLoadCallback(drawVis);
470
+
471
+ const classSafe = ${classSafe.toString()}
472
+
473
+ var selectedTab = classSafe("${this.tabs[0]}")
474
+ const tokens = {}
475
+ const visualisationData = [${this.visualisationData.join(',')}]
476
+
477
+ const _sort = ${_sort.toString()}
478
+ const createElement = ${createElement.toString()}
479
+
480
+ function drawVis(tab) {
481
+ if (tab) {
482
+ if (selectedTab) document.getElementById(selectedTab).classList.remove('selectedTab')
483
+ selectedTab = tab
484
+ } else if (${this.tabs.length > 0}) {
485
+ selectedTab = classSafe('${this.tabs[0]}')
486
+ }
487
+
488
+ if (selectedTab) document.getElementById(selectedTab).classList.add('selectedTab')
489
+ document.getElementById('content').innerHTML = ''
490
+ ${this.visualisations.map(([name, type, visualisationOptions, dataIndex, graphFlags]) => {
491
+ return `createElement('${name}', '${type}', ${JSON.stringify(visualisationOptions)} ,${dataIndex}, ${JSON.stringify(graphFlags)})`
492
+ })}
493
+ }
494
+
495
+ </script>
496
+ </head>
497
+ <body>
498
+ ${this.tabs.length > 0 ? `<div class='tabBar'>
499
+ ${this.tabs.map(tab => `<div id=${classSafe(tab)} class='tabs' onclick="drawVis('${classSafe(tab)}')">${tab}</div>`).join("\n")}
500
+ </div>` : ''}
501
+
502
+ <div id='content'>
503
+ </div>
504
+ </body>
505
+ </html>
506
+ `)
507
+
508
+ console.log('File ouput created ', filePath)
509
+ }
510
+ }
511
+
512
+ module.exports = { Vaporous, Aggregation, By, Window }
@@ -0,0 +1,100 @@
1
+ {"deviceFirmware":"v0","deviceId":"Alice","temp":36.219887323456646,"humidity":30.297717422881135,"energySpend":-60.63554463614166,"timestamp":"2025-09-26T08:44:34.413Z"}
2
+ {"deviceFirmware":"v1","deviceId":"Bob","temp":-5.9232478898979295,"humidity":82.80675544622181,"energySpend":-89.56512475525754,"timestamp":"2025-09-25T14:33:18.873Z"}
3
+ {"deviceFirmware":"v2","deviceId":"Charlie","temp":20.2051687423088,"humidity":69.6784447724362,"energySpend":-67.10225482688506,"timestamp":"2025-09-25T05:52:18.094Z"}
4
+ {"deviceFirmware":"v0","deviceId":"David","temp":20.948234789947996,"humidity":98.20920240392792,"energySpend":32.88567561430975,"timestamp":"2025-09-25T21:50:06.754Z"}
5
+ {"deviceFirmware":"v1","deviceId":"Eve","temp":31.181673531763707,"humidity":25.744929223727084,"energySpend":-67.23945272360089,"timestamp":"2025-09-24T21:45:43.386Z"}
6
+ {"deviceFirmware":"v2","deviceId":"Frank","temp":20.234985663517,"humidity":73.42388894957857,"energySpend":48.69713990500978,"timestamp":"2025-09-25T05:50:08.181Z"}
7
+ {"deviceFirmware":"v0","deviceId":"Grace","temp":-1.326455624720431,"humidity":58.41476355444222,"energySpend":53.75752048675275,"timestamp":"2025-09-26T07:48:28.156Z"}
8
+ {"deviceFirmware":"v1","deviceId":"Heidi","temp":-8.082193600329923,"humidity":76.06394564638288,"energySpend":-53.068824093096424,"timestamp":"2025-09-25T00:41:46.871Z"}
9
+ {"deviceFirmware":"v2","deviceId":"Ivan","temp":20.770525779843197,"humidity":3.292019207228904,"energySpend":17.623271057988916,"timestamp":"2025-09-25T23:13:18.669Z"}
10
+ {"deviceFirmware":"v0","deviceId":"Judy","temp":23.697227476580586,"humidity":21.970977064841364,"energySpend":94.92375332440508,"timestamp":"2025-09-25T10:02:06.557Z"}
11
+ {"deviceFirmware":"v1","deviceId":"Alice","temp":23.252501992633967,"humidity":30.980427007736267,"energySpend":-8.198810094664879,"timestamp":"2025-09-25T00:52:41.482Z"}
12
+ {"deviceFirmware":"v2","deviceId":"Bob","temp":38.818588757341445,"humidity":97.98807162278666,"energySpend":15.107463038260377,"timestamp":"2025-09-25T21:03:47.552Z"}
13
+ {"deviceFirmware":"v0","deviceId":"Charlie","temp":12.710427831776961,"humidity":67.58511721952598,"energySpend":30.158069577337358,"timestamp":"2025-09-25T12:42:20.848Z"}
14
+ {"deviceFirmware":"v1","deviceId":"David","temp":-2.8468429723613724,"humidity":53.56764511358813,"energySpend":75.3197860902797,"timestamp":"2025-09-24T19:42:28.683Z"}
15
+ {"deviceFirmware":"v2","deviceId":"Eve","temp":-9.715177706403182,"humidity":8.736778103561484,"energySpend":36.148597488258446,"timestamp":"2025-09-25T06:50:21.135Z"}
16
+ {"deviceFirmware":"v0","deviceId":"Frank","temp":1.8741584701701939,"humidity":89.06506163008038,"energySpend":-68.63677303088652,"timestamp":"2025-09-26T05:15:05.841Z"}
17
+ {"deviceFirmware":"v1","deviceId":"Grace","temp":7.909283563148094,"humidity":31.94178278264748,"energySpend":63.89139581923689,"timestamp":"2025-09-25T02:59:28.453Z"}
18
+ {"deviceFirmware":"v2","deviceId":"Heidi","temp":-14.129012195715141,"humidity":34.153498592626974,"energySpend":63.840127125016494,"timestamp":"2025-09-25T12:00:37.338Z"}
19
+ {"deviceFirmware":"v0","deviceId":"Ivan","temp":4.837105154299508,"humidity":58.87773909158619,"energySpend":7.174041495181115,"timestamp":"2025-09-24T22:32:27.521Z"}
20
+ {"deviceFirmware":"v1","deviceId":"Judy","temp":-14.641789239109414,"humidity":83.92609330126157,"energySpend":21.449941833850602,"timestamp":"2025-09-25T06:57:38.381Z"}
21
+ {"deviceFirmware":"v2","deviceId":"Alice","temp":22.619624991885516,"humidity":30.978794770973693,"energySpend":71.03630530620032,"timestamp":"2025-09-25T23:52:30.540Z"}
22
+ {"deviceFirmware":"v0","deviceId":"Bob","temp":22.937772397208413,"humidity":81.89011599718975,"energySpend":-55.16579843970298,"timestamp":"2025-09-25T21:05:54.351Z"}
23
+ {"deviceFirmware":"v1","deviceId":"Charlie","temp":38.83324070089762,"humidity":65.55935136576446,"energySpend":-42.78991748797743,"timestamp":"2025-09-25T17:18:54.498Z"}
24
+ {"deviceFirmware":"v2","deviceId":"David","temp":-5.493554502952563,"humidity":11.986596959695731,"energySpend":-45.49275667282356,"timestamp":"2025-09-24T17:19:11.680Z"}
25
+ {"deviceFirmware":"v0","deviceId":"Eve","temp":-0.4641207797254978,"humidity":44.658699367061274,"energySpend":70.95225767426743,"timestamp":"2025-09-26T11:27:14.429Z"}
26
+ {"deviceFirmware":"v1","deviceId":"Frank","temp":23.4793320605661,"humidity":36.38235362727917,"energySpend":-26.952740520534775,"timestamp":"2025-09-26T11:41:19.326Z"}
27
+ {"deviceFirmware":"v2","deviceId":"Grace","temp":7.844289411230378,"humidity":96.15715052169116,"energySpend":-83.0712562086999,"timestamp":"2025-09-26T06:49:17.757Z"}
28
+ {"deviceFirmware":"v0","deviceId":"Heidi","temp":-3.2994897030685237,"humidity":66.00950090187865,"energySpend":-55.75856868850085,"timestamp":"2025-09-26T10:22:23.747Z"}
29
+ {"deviceFirmware":"v1","deviceId":"Ivan","temp":19.49145567581013,"humidity":45.12254261788631,"energySpend":4.083777984475773,"timestamp":"2025-09-25T18:55:39.493Z"}
30
+ {"deviceFirmware":"v2","deviceId":"Judy","temp":-0.2986415872979862,"humidity":69.64352587603213,"energySpend":67.74617757587208,"timestamp":"2025-09-26T00:18:24.589Z"}
31
+ {"deviceFirmware":"v0","deviceId":"Alice","temp":24.675121658315106,"humidity":88.4361736417057,"energySpend":24.151138955324697,"timestamp":"2025-09-26T10:44:14.426Z"}
32
+ {"deviceFirmware":"v1","deviceId":"Bob","temp":32.94741968587517,"humidity":24.739444790461306,"energySpend":89.71508564594822,"timestamp":"2025-09-25T04:27:40.176Z"}
33
+ {"deviceFirmware":"v2","deviceId":"Charlie","temp":27.626026522323492,"humidity":89.9581839954813,"energySpend":62.53162105565798,"timestamp":"2025-09-25T02:54:08.865Z"}
34
+ {"deviceFirmware":"v0","deviceId":"David","temp":27.779194834299517,"humidity":45.93039749121925,"energySpend":74.43189231779263,"timestamp":"2025-09-26T06:43:10.455Z"}
35
+ {"deviceFirmware":"v1","deviceId":"Eve","temp":15.476024460932969,"humidity":48.14235133301945,"energySpend":-45.81448559399189,"timestamp":"2025-09-25T18:22:01.378Z"}
36
+ {"deviceFirmware":"v2","deviceId":"Frank","temp":-10.725655628699197,"humidity":38.754057125418726,"energySpend":-62.34171998623604,"timestamp":"2025-09-25T04:59:50.203Z"}
37
+ {"deviceFirmware":"v0","deviceId":"Grace","temp":-13.370084109737386,"humidity":42.04547605048259,"energySpend":48.41018290736562,"timestamp":"2025-09-24T20:28:38.422Z"}
38
+ {"deviceFirmware":"v1","deviceId":"Heidi","temp":29.338483497440798,"humidity":13.08133536381235,"energySpend":-73.11653177873701,"timestamp":"2025-09-25T03:52:32.942Z"}
39
+ {"deviceFirmware":"v2","deviceId":"Ivan","temp":30.96459638821988,"humidity":12.107557761784584,"energySpend":-23.41997587425989,"timestamp":"2025-09-25T14:19:59.636Z"}
40
+ {"deviceFirmware":"v0","deviceId":"Judy","temp":13.06368044373329,"humidity":92.46778597318215,"energySpend":18.513018727081658,"timestamp":"2025-09-25T17:56:10.969Z"}
41
+ {"deviceFirmware":"v1","deviceId":"Alice","temp":27.625597115509855,"humidity":93.88887460315067,"energySpend":109.73806819156545,"timestamp":"2025-09-24T17:43:46.223Z"}
42
+ {"deviceFirmware":"v2","deviceId":"Bob","temp":13.656823036912513,"humidity":3.265209449181313,"energySpend":79.99467223596784,"timestamp":"2025-09-26T13:03:07.272Z"}
43
+ {"deviceFirmware":"v0","deviceId":"Charlie","temp":-13.18123604222745,"humidity":38.961226824147445,"energySpend":107.3533767987048,"timestamp":"2025-09-26T06:57:11.462Z"}
44
+ {"deviceFirmware":"v1","deviceId":"David","temp":38.40878084741347,"humidity":45.65568398192157,"energySpend":7.579215536732633,"timestamp":"2025-09-25T20:14:31.578Z"}
45
+ {"deviceFirmware":"v2","deviceId":"Eve","temp":-8.111209463582192,"humidity":66.4948709643353,"energySpend":-78.89561890505382,"timestamp":"2025-09-25T13:57:11.398Z"}
46
+ {"deviceFirmware":"v0","deviceId":"Frank","temp":-7.668238325415306,"humidity":35.4912520324437,"energySpend":-5.04964675343534,"timestamp":"2025-09-25T13:30:33.062Z"}
47
+ {"deviceFirmware":"v1","deviceId":"Grace","temp":26.237427599022126,"humidity":28.797077485257603,"energySpend":107.52060067780928,"timestamp":"2025-09-25T18:53:56.342Z"}
48
+ {"deviceFirmware":"v2","deviceId":"Heidi","temp":23.59850891879899,"humidity":24.830771885038985,"energySpend":-77.41780272170567,"timestamp":"2025-09-26T06:41:00.354Z"}
49
+ {"deviceFirmware":"v0","deviceId":"Ivan","temp":24.05472123513595,"humidity":19.917801938502095,"energySpend":55.05274857384043,"timestamp":"2025-09-26T10:21:05.283Z"}
50
+ {"deviceFirmware":"v1","deviceId":"Judy","temp":32.35646426053241,"humidity":84.51419978089541,"energySpend":43.680002231848505,"timestamp":"2025-09-26T11:48:06.792Z"}
51
+ {"deviceFirmware":"v2","deviceId":"Alice","temp":35.79163740549359,"humidity":60.38861906903459,"energySpend":20.59050521188651,"timestamp":"2025-09-26T02:18:07.667Z"}
52
+ {"deviceFirmware":"v0","deviceId":"Bob","temp":9.671817703550673,"humidity":77.02262469559429,"energySpend":19.701209898203274,"timestamp":"2025-09-26T01:19:25.182Z"}
53
+ {"deviceFirmware":"v1","deviceId":"Charlie","temp":24.10472638062074,"humidity":68.55170287242271,"energySpend":62.282234537480264,"timestamp":"2025-09-24T18:26:48.875Z"}
54
+ {"deviceFirmware":"v2","deviceId":"David","temp":-4.14882166464508,"humidity":7.959582995606773,"energySpend":-57.825888526603826,"timestamp":"2025-09-26T05:44:36.522Z"}
55
+ {"deviceFirmware":"v0","deviceId":"Eve","temp":15.740181812767084,"humidity":38.96627591819967,"energySpend":-72.0311570679761,"timestamp":"2025-09-25T11:24:48.416Z"}
56
+ {"deviceFirmware":"v1","deviceId":"Frank","temp":-4.971741629779427,"humidity":30.527616468873816,"energySpend":-55.97921030790679,"timestamp":"2025-09-24T15:37:13.467Z"}
57
+ {"deviceFirmware":"v2","deviceId":"Grace","temp":38.98350031689613,"humidity":93.09158404305018,"energySpend":-30.82500052587146,"timestamp":"2025-09-24T15:02:52.733Z"}
58
+ {"deviceFirmware":"v0","deviceId":"Heidi","temp":36.491472774817254,"humidity":61.38167903929906,"energySpend":48.74668509730466,"timestamp":"2025-09-26T11:25:12.202Z"}
59
+ {"deviceFirmware":"v1","deviceId":"Ivan","temp":27.68942852259262,"humidity":98.62491881130723,"energySpend":34.88693335076283,"timestamp":"2025-09-25T22:34:40.470Z"}
60
+ {"deviceFirmware":"v2","deviceId":"Judy","temp":30.041240032203596,"humidity":18.316356443441208,"energySpend":13.324090769427542,"timestamp":"2025-09-26T14:07:10.520Z"}
61
+ {"deviceFirmware":"v0","deviceId":"Alice","temp":-2.4012422960472968,"humidity":8.190725332625277,"energySpend":16.55654992218477,"timestamp":"2025-09-25T02:52:06.020Z"}
62
+ {"deviceFirmware":"v1","deviceId":"Bob","temp":27.86834052105285,"humidity":80.52347038706793,"energySpend":20.968578666346176,"timestamp":"2025-09-26T06:53:46.773Z"}
63
+ {"deviceFirmware":"v2","deviceId":"Charlie","temp":6.916587218840149,"humidity":25.290739491920135,"energySpend":74.10295911524634,"timestamp":"2025-09-25T05:58:35.119Z"}
64
+ {"deviceFirmware":"v0","deviceId":"David","temp":14.430736844272886,"humidity":46.11038787377461,"energySpend":59.7423234154231,"timestamp":"2025-09-25T23:40:29.590Z"}
65
+ {"deviceFirmware":"v1","deviceId":"Eve","temp":3.678257322301647,"humidity":62.813047032430745,"energySpend":-11.451267814467329,"timestamp":"2025-09-26T07:16:08.111Z"}
66
+ {"deviceFirmware":"v2","deviceId":"Frank","temp":31.02706044094495,"humidity":7.178153002981816,"energySpend":-0.24137227826744834,"timestamp":"2025-09-24T17:48:35.589Z"}
67
+ {"deviceFirmware":"v0","deviceId":"Grace","temp":-2.0722355453449417,"humidity":15.459883660568067,"energySpend":89.0849921301305,"timestamp":"2025-09-24T14:35:04.825Z"}
68
+ {"deviceFirmware":"v1","deviceId":"Heidi","temp":32.82921657399053,"humidity":37.185341141758514,"energySpend":38.36931090337936,"timestamp":"2025-09-25T08:40:46.709Z"}
69
+ {"deviceFirmware":"v2","deviceId":"Ivan","temp":-6.570785946849789,"humidity":46.74892706181552,"energySpend":-25.030601954079586,"timestamp":"2025-09-25T20:29:56.925Z"}
70
+ {"deviceFirmware":"v0","deviceId":"Judy","temp":1.1701226927250943,"humidity":8.256480313856088,"energySpend":95.86172375071652,"timestamp":"2025-09-24T17:54:50.589Z"}
71
+ {"deviceFirmware":"v1","deviceId":"Alice","temp":18.690676364350963,"humidity":1.4277643995089804,"energySpend":-8.597460817536188,"timestamp":"2025-09-25T16:02:20.012Z"}
72
+ {"deviceFirmware":"v2","deviceId":"Bob","temp":-15.25237348718426,"humidity":59.379411644965444,"energySpend":17.33237434575686,"timestamp":"2025-09-25T11:13:13.896Z"}
73
+ {"deviceFirmware":"v0","deviceId":"Charlie","temp":-13.33398490172737,"humidity":44.0452226638629,"energySpend":-33.29635608224018,"timestamp":"2025-09-24T23:01:53.944Z"}
74
+ {"deviceFirmware":"v1","deviceId":"David","temp":-2.512546145059545,"humidity":36.66984201581896,"energySpend":57.15881013946972,"timestamp":"2025-09-25T16:50:59.859Z"}
75
+ {"deviceFirmware":"v2","deviceId":"Eve","temp":3.8699635506237513,"humidity":60.53856968231888,"energySpend":53.45983188233697,"timestamp":"2025-09-26T00:47:15.776Z"}
76
+ {"deviceFirmware":"v0","deviceId":"Frank","temp":-9.970088256734456,"humidity":58.99925503233575,"energySpend":68.00839130281278,"timestamp":"2025-09-25T00:08:20.649Z"}
77
+ {"deviceFirmware":"v1","deviceId":"Grace","temp":11.964021387217272,"humidity":62.257764666479574,"energySpend":-1.5999563064975462,"timestamp":"2025-09-25T08:39:03.073Z"}
78
+ {"deviceFirmware":"v2","deviceId":"Heidi","temp":-4.298569679149857,"humidity":72.6710669572898,"energySpend":76.63430577511446,"timestamp":"2025-09-25T00:56:05.834Z"}
79
+ {"deviceFirmware":"v0","deviceId":"Ivan","temp":-1.4971338345191327,"humidity":73.07049999504225,"energySpend":-30.43720462397968,"timestamp":"2025-09-25T22:03:57.395Z"}
80
+ {"deviceFirmware":"v1","deviceId":"Judy","temp":-12.13765552525178,"humidity":98.95108065958533,"energySpend":16.491098247320664,"timestamp":"2025-09-26T05:34:55.444Z"}
81
+ {"deviceFirmware":"v2","deviceId":"Alice","temp":-0.4269109276584935,"humidity":76.45851769024388,"energySpend":32.736694809717534,"timestamp":"2025-09-25T07:08:11.914Z"}
82
+ {"deviceFirmware":"v0","deviceId":"Bob","temp":34.524468390172785,"humidity":85.2005409246101,"energySpend":89.92398391087002,"timestamp":"2025-09-25T22:24:37.687Z"}
83
+ {"deviceFirmware":"v1","deviceId":"Charlie","temp":15.938720780164118,"humidity":43.08215782022418,"energySpend":-65.88246142637885,"timestamp":"2025-09-25T05:02:12.013Z"}
84
+ {"deviceFirmware":"v2","deviceId":"David","temp":4.712417012784535,"humidity":3.309930643336976,"energySpend":57.462032184626764,"timestamp":"2025-09-25T03:58:16.549Z"}
85
+ {"deviceFirmware":"v0","deviceId":"Eve","temp":34.86417535329251,"humidity":84.1608937889622,"energySpend":109.29203399739862,"timestamp":"2025-09-26T02:35:18.053Z"}
86
+ {"deviceFirmware":"v1","deviceId":"Frank","temp":6.773020163874222,"humidity":70.56634560187788,"energySpend":86.66440593226099,"timestamp":"2025-09-25T14:56:55.008Z"}
87
+ {"deviceFirmware":"v2","deviceId":"Grace","temp":21.496070437686384,"humidity":80.61579351399172,"energySpend":51.10831782133641,"timestamp":"2025-09-26T11:13:08.192Z"}
88
+ {"deviceFirmware":"v0","deviceId":"Heidi","temp":16.424832969774478,"humidity":92.98341964858608,"energySpend":92.92299658924759,"timestamp":"2025-09-26T05:23:12.170Z"}
89
+ {"deviceFirmware":"v1","deviceId":"Ivan","temp":30.22878811710862,"humidity":52.7180743757806,"energySpend":-66.93175597574862,"timestamp":"2025-09-24T15:49:45.616Z"}
90
+ {"deviceFirmware":"v2","deviceId":"Judy","temp":33.121264304309506,"humidity":94.03851140019344,"energySpend":-8.173182704062143,"timestamp":"2025-09-25T12:04:48.115Z"}
91
+ {"deviceFirmware":"v0","deviceId":"Alice","temp":35.21094475655816,"humidity":23.730285730827745,"energySpend":100.72349392977753,"timestamp":"2025-09-26T01:34:52.843Z"}
92
+ {"deviceFirmware":"v1","deviceId":"Bob","temp":-10.199531227657303,"humidity":86.70553548230407,"energySpend":-77.51225988054006,"timestamp":"2025-09-26T05:11:57.980Z"}
93
+ {"deviceFirmware":"v2","deviceId":"Charlie","temp":-15.84459754736999,"humidity":2.502236174960082,"energySpend":82.26037631490496,"timestamp":"2025-09-24T17:56:48.827Z"}
94
+ {"deviceFirmware":"v0","deviceId":"David","temp":3.760883387837602,"humidity":22.29245235714663,"energySpend":86.0581852980911,"timestamp":"2025-09-25T02:26:13.055Z"}
95
+ {"deviceFirmware":"v1","deviceId":"Eve","temp":19.856591625626194,"humidity":55.09250244447449,"energySpend":-32.68509375397507,"timestamp":"2025-09-24T15:57:58.068Z"}
96
+ {"deviceFirmware":"v2","deviceId":"Frank","temp":32.902711837760755,"humidity":66.79442463203338,"energySpend":25.314481306365607,"timestamp":"2025-09-24T23:47:44.908Z"}
97
+ {"deviceFirmware":"v0","deviceId":"Grace","temp":31.74195945835683,"humidity":61.71436860809574,"energySpend":39.72130494078334,"timestamp":"2025-09-24T18:37:49.347Z"}
98
+ {"deviceFirmware":"v1","deviceId":"Heidi","temp":-4.690759084455401,"humidity":14.041388326618787,"energySpend":84.37924886680594,"timestamp":"2025-09-24T21:03:26.335Z"}
99
+ {"deviceFirmware":"v2","deviceId":"Ivan","temp":19.666666595802383,"humidity":92.87478235649547,"energySpend":102.76348878147513,"timestamp":"2025-09-24T23:43:33.164Z"}
100
+ {"deviceFirmware":"v0","deviceId":"Judy","temp":20.686109191309193,"humidity":22.15572214639012,"energySpend":-57.06968050999724,"timestamp":"2025-09-25T11:46:07.808Z"}