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 +512 -0
- package/examples/sensors/exampleData/temp_0_10.jsonStream +100 -0
- package/examples/sensors/exampleData/temp_1_10.jsonStream +100 -0
- package/examples/sensors/exampleData/temp_2_10.jsonStream +100 -0
- package/examples/sensors/exampleData/temp_3_10.jsonStream +100 -0
- package/examples/sensors/exampleData/temp_4_10.jsonStream +100 -0
- package/examples/sensors/exampleData/temp_5_10.jsonStream +100 -0
- package/examples/sensors/exampleData/temp_6_10.jsonStream +100 -0
- package/examples/sensors/exampleData/temp_7_10.jsonStream +100 -0
- package/examples/sensors/exampleData/temp_8_10.jsonStream +100 -0
- package/examples/sensors/exampleData/temp_9_10.jsonStream +100 -0
- package/examples/sensors/query.js +98 -0
- package/examples/sensors/run.sh +2 -0
- package/examples/sensors/sensor_data.js +55 -0
- package/package.json +15 -0
- package/styles.css +79 -0
- package/types/Aggregation.js +53 -0
- package/types/By.js +7 -0
- package/types/Window.js +7 -0
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"}
|