node-red-contrib-power-saver 5.0.0-beta.4 → 5.0.0-beta.6
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/.github/workflows/publish-to-npm.yml +7 -6
- package/docs/changelog/README.md +10 -0
- package/docs/nodes/ps-strategy-best-save.md +25 -8
- package/docs/nodes/ps-strategy-lowest-price.md +9 -6
- package/package.json +1 -1
- package/src/handle-output.js +37 -2
- package/src/strategy-best-save-functions.js +11 -17
- package/src/utils.js +133 -15
- package/test/data/best-save-performance-price-data.js +197 -0
- package/test/data/bug-231-input.json +197 -0
- package/test/data/bug-231-output.json +1035 -0
- package/test/data/merge-schedule-data.js +26 -0
- package/test/strategy-best-save-bug-232.test.js +0 -2
- package/test/strategy-best-save-performance.test.js +79 -0
- package/test/strategy-lowest-price-bug231.test.js +98 -0
- package/test/strategy-lowest-price.test.js +1 -1
- package/test/test-utils.js +1 -1
- package/test/utils.test.js +59 -0
- package/= +0 -0
|
@@ -3,21 +3,22 @@ name: Publish to npmjs
|
|
|
3
3
|
on:
|
|
4
4
|
release:
|
|
5
5
|
types: [created]
|
|
6
|
+
permissions:
|
|
7
|
+
id-token: write
|
|
8
|
+
contents: read
|
|
6
9
|
jobs:
|
|
7
10
|
build:
|
|
8
11
|
runs-on: ubuntu-latest
|
|
9
12
|
steps:
|
|
10
|
-
- uses: actions/checkout@
|
|
13
|
+
- uses: actions/checkout@v4
|
|
11
14
|
# Setup .npmrc file to publish to npm
|
|
12
|
-
- uses: actions/setup-node@
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
13
16
|
with:
|
|
14
|
-
node-version: '
|
|
17
|
+
node-version: '24.x'
|
|
15
18
|
registry-url: 'https://registry.npmjs.org'
|
|
16
19
|
- uses: szenius/set-timezone@v1.0
|
|
17
20
|
with:
|
|
18
21
|
timezoneLinux: "Europe/Oslo"
|
|
19
22
|
- run: npm ci
|
|
20
23
|
- run: 'npm run test'
|
|
21
|
-
- run: npm publish
|
|
22
|
-
env:
|
|
23
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
24
|
+
- run: npm publish --tag beta --provenance
|
package/docs/changelog/README.md
CHANGED
|
@@ -7,6 +7,16 @@ sidebarDepth: 1
|
|
|
7
7
|
|
|
8
8
|
List the most significant changes.
|
|
9
9
|
|
|
10
|
+
## 5.0.0.beta.6
|
|
11
|
+
|
|
12
|
+
- Best Save Performance improvement
|
|
13
|
+
|
|
14
|
+
## 5.0.0.beta.5
|
|
15
|
+
|
|
16
|
+
- Fix Lowest Price so on-periods are not split up if they can go together
|
|
17
|
+
- Collapse minutes array on output 3 (NB! BREAKING CHANGE)
|
|
18
|
+
- Remove log line "Switching off in x milliseconds"
|
|
19
|
+
|
|
10
20
|
## 5.0.0.beta.4
|
|
11
21
|
|
|
12
22
|
- Fix bug on recovery time when recoveryMaxMinutes is not set
|
|
@@ -135,23 +135,40 @@ Example of output:
|
|
|
135
135
|
}
|
|
136
136
|
],
|
|
137
137
|
"minutes": [
|
|
138
|
-
|
|
138
|
+
{
|
|
139
139
|
"start": "2025-09-30T00:00:00.000+02:00",
|
|
140
140
|
"price": 0.2129,
|
|
141
141
|
"onOff": false,
|
|
142
|
-
"saving": 0.0631
|
|
142
|
+
"saving": 0.0631,
|
|
143
|
+
"count": 15
|
|
143
144
|
},
|
|
144
145
|
{
|
|
145
|
-
"start": "2025-09-30T00:
|
|
146
|
-
"price": 0.
|
|
146
|
+
"start": "2025-09-30T00:15:00.000+02:00",
|
|
147
|
+
"price": 0.2127,
|
|
147
148
|
"onOff": false,
|
|
148
|
-
"saving": 0.
|
|
149
|
+
"saving": 0.0633,
|
|
150
|
+
"count": 15
|
|
149
151
|
},
|
|
150
152
|
{
|
|
151
|
-
"start": "2025-09-30T00:
|
|
152
|
-
"price": 0.
|
|
153
|
+
"start": "2025-09-30T00:30:00.000+02:00",
|
|
154
|
+
"price": 0.2231,
|
|
153
155
|
"onOff": false,
|
|
154
|
-
"saving": 0.
|
|
156
|
+
"saving": 0.0529,
|
|
157
|
+
"count": 15
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"start": "2025-09-30T00:45:00.000+02:00",
|
|
161
|
+
"price": 0.2235,
|
|
162
|
+
"onOff": false,
|
|
163
|
+
"saving": 0.0533,
|
|
164
|
+
"count": 15
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"start": "2025-09-30T01:00:00.000+02:00",
|
|
168
|
+
"price": 0.2760,
|
|
169
|
+
"onOff": true,
|
|
170
|
+
"saving": null,
|
|
171
|
+
"count": 1380
|
|
155
172
|
}, // ...
|
|
156
173
|
],
|
|
157
174
|
"source": "Nord Pool",
|
|
@@ -181,20 +181,23 @@ Example of output:
|
|
|
181
181
|
"start": "2025-10-07T00:00:00.000+02:00",
|
|
182
182
|
"price": 0.1875,
|
|
183
183
|
"onOff": false,
|
|
184
|
-
"saving": null
|
|
184
|
+
"saving": null,
|
|
185
|
+
"count": 15
|
|
185
186
|
},
|
|
186
187
|
{
|
|
187
|
-
"start": "2025-10-07T00:
|
|
188
|
-
"price": 0.
|
|
188
|
+
"start": "2025-10-07T00:15:00.000+02:00",
|
|
189
|
+
"price": 0.1882,
|
|
189
190
|
"onOff": false,
|
|
190
|
-
"saving": null
|
|
191
|
+
"saving": null,
|
|
192
|
+
"count": 15
|
|
191
193
|
},
|
|
192
194
|
//...
|
|
193
195
|
{
|
|
194
|
-
"start": "2025-10-
|
|
196
|
+
"start": "2025-10-08T23:45:00.000+02:00",
|
|
195
197
|
"price": 0.2578,
|
|
196
198
|
"onOff": false,
|
|
197
|
-
"saving": null
|
|
199
|
+
"saving": null,
|
|
200
|
+
"count": 15
|
|
198
201
|
}
|
|
199
202
|
],
|
|
200
203
|
"source": "Tibber",
|
package/package.json
CHANGED
package/src/handle-output.js
CHANGED
|
@@ -20,7 +20,7 @@ function handleOutput(node, config, plan, outputCommands, planFromTime) {
|
|
|
20
20
|
let output3 = {
|
|
21
21
|
payload: {
|
|
22
22
|
schedule: plan.schedule,
|
|
23
|
-
minutes: plan.minutes,
|
|
23
|
+
minutes: collapseMinutes(plan.minutes),
|
|
24
24
|
source: plan.source,
|
|
25
25
|
config,
|
|
26
26
|
time: planFromTime.toISO(),
|
|
@@ -74,7 +74,6 @@ function runSchedule(node, schedule, time, currentSent = false) {
|
|
|
74
74
|
const nextTime = DateTime.fromISO(entry.time);
|
|
75
75
|
const wait = nextTime - time;
|
|
76
76
|
const onOff = entry.value ? "on" : "off";
|
|
77
|
-
node.log("Switching " + onOff + " in " + wait + " milliseconds");
|
|
78
77
|
const statusMessage = `${remainingSchedule.length} changes - ${
|
|
79
78
|
remainingSchedule[0].value ? "on" : "off"
|
|
80
79
|
} at ${nextTime.toFormat("HH:mm")}`;
|
|
@@ -110,6 +109,42 @@ function strategyShallSendSchedule(msg, commands) {
|
|
|
110
109
|
return msgHasConfig(msg) || msgHasPriceData(msg) || commands.replan;
|
|
111
110
|
}
|
|
112
111
|
|
|
112
|
+
function collapseMinutes(minutes) {
|
|
113
|
+
function itemsEqual(a, b) {
|
|
114
|
+
return a.price === b.price && a.onOff === b.onOff && a.saving === b.saving;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
if (!Array.isArray(minutes) || minutes.length === 0) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const result = [];
|
|
125
|
+
let currentValue = minutes[0];
|
|
126
|
+
let count = 1;
|
|
127
|
+
let startIndex = 0;
|
|
128
|
+
|
|
129
|
+
for (let i = 1; i < minutes.length; i++) {
|
|
130
|
+
if (itemsEqual(minutes[i], currentValue)) {
|
|
131
|
+
count++;
|
|
132
|
+
} else {
|
|
133
|
+
result.push({ ...currentValue, count, startIndex });
|
|
134
|
+
currentValue = minutes[i];
|
|
135
|
+
count = 1;
|
|
136
|
+
startIndex = i;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
result.push({ ...currentValue, count, startIndex });
|
|
141
|
+
|
|
142
|
+
return result;
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
113
148
|
module.exports = {
|
|
114
149
|
handleOutput,
|
|
115
150
|
shallSendOutput,
|
|
@@ -13,12 +13,7 @@ const { fillArray } = require("./utils");
|
|
|
13
13
|
* @param {*} recoveryMaxMinutes Maximum recovery time in minutes
|
|
14
14
|
* @returns
|
|
15
15
|
*/
|
|
16
|
-
function isOnOffSequencesOk(
|
|
17
|
-
onOff,
|
|
18
|
-
maxMinutesOff,
|
|
19
|
-
minMinutesOff,
|
|
20
|
-
recoveryPercentage,
|
|
21
|
-
recoveryMaxMinutes = null) {
|
|
16
|
+
function isOnOffSequencesOk(onOff, maxMinutesOff, minMinutesOff, recoveryPercentage, recoveryMaxMinutes = null) {
|
|
22
17
|
let offCount = 0;
|
|
23
18
|
let onCount = 0;
|
|
24
19
|
let reachedMaxOff = false;
|
|
@@ -30,10 +25,10 @@ function isOnOffSequencesOk(
|
|
|
30
25
|
if (maxMinutesOff === 0 || reachedMaxOff) {
|
|
31
26
|
return false;
|
|
32
27
|
}
|
|
33
|
-
if(!reachedMinOn) {
|
|
28
|
+
if (!reachedMinOn) {
|
|
34
29
|
return false;
|
|
35
30
|
}
|
|
36
|
-
if(reachedMinOff === null) {
|
|
31
|
+
if (reachedMinOff === null) {
|
|
37
32
|
reachedMinOff = false;
|
|
38
33
|
}
|
|
39
34
|
offCount++;
|
|
@@ -44,16 +39,16 @@ function isOnOffSequencesOk(
|
|
|
44
39
|
if (offCount >= minMinutesOff) {
|
|
45
40
|
reachedMinOff = true;
|
|
46
41
|
}
|
|
47
|
-
const minRounded = Math.max(Math.round(offCount * recoveryPercentage / 100), 1)
|
|
42
|
+
const minRounded = Math.max(Math.round((offCount * recoveryPercentage) / 100), 1);
|
|
48
43
|
const recMaxMin = recoveryMaxMinutes === "" ? null : recoveryMaxMinutes;
|
|
49
|
-
minOnAfterOff = Math.min(minRounded, recMaxMin ?? minRounded)
|
|
50
|
-
if(i === onOff.length - 1) {
|
|
44
|
+
minOnAfterOff = Math.min(minRounded, recMaxMin ?? minRounded);
|
|
45
|
+
if (i === onOff.length - 1) {
|
|
51
46
|
// If last minute, consider min reached
|
|
52
47
|
reachedMinOn = true;
|
|
53
48
|
reachedMinOff = true;
|
|
54
49
|
}
|
|
55
50
|
} else {
|
|
56
|
-
if(reachedMinOff === false) {
|
|
51
|
+
if (reachedMinOff === false) {
|
|
57
52
|
return false;
|
|
58
53
|
}
|
|
59
54
|
onCount++;
|
|
@@ -123,12 +118,12 @@ function calculate(
|
|
|
123
118
|
}
|
|
124
119
|
}
|
|
125
120
|
|
|
126
|
-
savingsList.sort((
|
|
121
|
+
savingsList.sort((b, a) => (b.saving === a.saving ? a.count - b.count : b.saving - a.saving));
|
|
127
122
|
let onOff = values.map((v) => true); // Start with all on
|
|
128
123
|
|
|
129
124
|
// Find the best possible sequences
|
|
130
125
|
while (savingsList.length > 0) {
|
|
131
|
-
const { minute, count } = savingsList[
|
|
126
|
+
const { minute, count } = savingsList[savingsList.length - 1];
|
|
132
127
|
const onOffCopy = [...onOff];
|
|
133
128
|
let alreadyTaken = false;
|
|
134
129
|
for (let c = 0; c < count; c++) {
|
|
@@ -137,12 +132,11 @@ function calculate(
|
|
|
137
132
|
}
|
|
138
133
|
onOff[minute + c] = false;
|
|
139
134
|
}
|
|
140
|
-
if (isOnOffSequencesOk([...dayBefore, ...onOff], maxMinutesOff, minMinutesOff, recoveryPercentage,
|
|
141
|
-
recoveryMaxMinutes) && !alreadyTaken) {
|
|
135
|
+
if ( isOnOffSequencesOk( [...dayBefore, ...onOff], maxMinutesOff, minMinutesOff, recoveryPercentage, recoveryMaxMinutes ) && !alreadyTaken ) {
|
|
142
136
|
savingsList = savingsList.filter((s) => s.minute < minute || s.minute >= minute + count);
|
|
143
137
|
} else {
|
|
144
138
|
onOff = [...onOffCopy];
|
|
145
|
-
savingsList.
|
|
139
|
+
savingsList.pop();
|
|
146
140
|
}
|
|
147
141
|
}
|
|
148
142
|
return onOff;
|
package/src/utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const cloneDeep = require("lodash.clonedeep");
|
|
1
2
|
const { DateTime } = require("luxon");
|
|
2
3
|
|
|
3
4
|
function booleanConfig(value) {
|
|
@@ -24,19 +25,133 @@ function saveOriginalConfig(node, originalConfig) {
|
|
|
24
25
|
* in sorted order. Highest value first.
|
|
25
26
|
*/
|
|
26
27
|
function sortedIndex(valueArr) {
|
|
27
|
-
const
|
|
28
|
-
|
|
28
|
+
const collapsed = collapseArr(valueArr);
|
|
29
|
+
const withNeighbours = addNeighbours(collapsed);
|
|
30
|
+
const sortedCollapsed = sortCollapsed(withNeighbours);
|
|
31
|
+
const res = [];
|
|
32
|
+
sortedCollapsed.forEach((group) => {
|
|
33
|
+
const start = group.internalOrder === "asc" ? 0 : group.count - 1;;
|
|
34
|
+
const end = group.internalOrder === "asc" ? group.count : -1;
|
|
35
|
+
const step = group.internalOrder === "asc" ? 1 : -1;
|
|
36
|
+
for (let j = start; j !== end; j += step) {
|
|
37
|
+
res.push(group.startIndex + j);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return res;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The valueArr contains values.
|
|
46
|
+
* Collapse consecutive same values into one.
|
|
47
|
+
* Return array with the collapsed values and their counts.
|
|
48
|
+
*
|
|
49
|
+
* @param {*} valueArr
|
|
50
|
+
*/
|
|
51
|
+
function collapseArr(valueArr, itemsEqual = (a, b) => a === b) {
|
|
52
|
+
if (!Array.isArray(valueArr) || valueArr.length === 0) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const result = [];
|
|
57
|
+
let currentValue = valueArr[0];
|
|
58
|
+
let count = 1;
|
|
59
|
+
let startIndex = 0;
|
|
60
|
+
|
|
61
|
+
for (let i = 1; i < valueArr.length; i++) {
|
|
62
|
+
if (itemsEqual(valueArr[i], currentValue)) {
|
|
63
|
+
count++;
|
|
64
|
+
} else {
|
|
65
|
+
result.push({ value: currentValue, count, startIndex });
|
|
66
|
+
currentValue = valueArr[i];
|
|
67
|
+
count = 1;
|
|
68
|
+
startIndex = i;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
result.push({ value: currentValue, count, startIndex });
|
|
73
|
+
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Takes a collapsed array (from collapseArr)
|
|
79
|
+
* and expands it back to the original value array.
|
|
80
|
+
*
|
|
81
|
+
* @param {*} collapsedArr
|
|
82
|
+
*/
|
|
83
|
+
function expandArr(collapsedArr) {
|
|
84
|
+
const result = [];
|
|
85
|
+
|
|
86
|
+
for (const { value, count } of collapsedArr) {
|
|
87
|
+
for (let i = 0; i < count; i++) {
|
|
88
|
+
result.push(value);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Take a collapsed array as input.
|
|
97
|
+
* Add a property 'before' and 'after' to each item:
|
|
98
|
+
* containing the value before and after in the original array,
|
|
99
|
+
* or null if there is none.
|
|
100
|
+
*
|
|
101
|
+
* @param {*} collapsedArr
|
|
102
|
+
*/
|
|
103
|
+
function addNeighbours(collapsedArr) {
|
|
104
|
+
if (!Array.isArray(collapsedArr)) return [];
|
|
105
|
+
|
|
106
|
+
return collapsedArr.map((item, index) => {
|
|
107
|
+
const before = index > 0 ? collapsedArr[index - 1].value : null;
|
|
108
|
+
const after = index < collapsedArr.length - 1 ? collapsedArr[index + 1].value : null;
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
...item,
|
|
112
|
+
before,
|
|
113
|
+
after,
|
|
114
|
+
};
|
|
29
115
|
});
|
|
30
|
-
|
|
31
|
-
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Sort records in collapsed array by value descending,
|
|
120
|
+
* then by count ascending, then by best of before and after descending.
|
|
121
|
+
* If before or after is null, null goes first
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
function sortCollapsed(collapsedArr) {
|
|
125
|
+
const sorted = cloneDeep(collapsedArr).sort((a, b) => {
|
|
126
|
+
// 1. value ascending
|
|
127
|
+
if (a.value !== b.value) {
|
|
128
|
+
return b.value - a.value;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 2. count descending
|
|
132
|
+
if (a.count !== b.count) {
|
|
133
|
+
return a.count - b.count;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 3. before or after
|
|
137
|
+
if (a.before === null || a.after === null) {
|
|
32
138
|
return -1;
|
|
33
139
|
}
|
|
34
|
-
if (
|
|
140
|
+
if (b.before === null || b.after === null) {
|
|
35
141
|
return 1;
|
|
36
142
|
}
|
|
37
|
-
|
|
143
|
+
const aBest = Math.max(a.before, a.after);
|
|
144
|
+
const bBest = Math.max(b.before, b.after);
|
|
145
|
+
|
|
146
|
+
return bBest - aBest;
|
|
38
147
|
});
|
|
39
|
-
|
|
148
|
+
|
|
149
|
+
// Set internal ordering for each line
|
|
150
|
+
sorted.forEach((item) => {
|
|
151
|
+
item.internalOrder =
|
|
152
|
+
item.after === null ? "desc" : item.before === null ? "asc" : item.after > item.before ? "desc" : "asc";
|
|
153
|
+
});
|
|
154
|
+
return sorted
|
|
40
155
|
}
|
|
41
156
|
|
|
42
157
|
/**
|
|
@@ -105,8 +220,8 @@ function loadDayData(node, date) {
|
|
|
105
220
|
schedule: [],
|
|
106
221
|
minutes: [],
|
|
107
222
|
};
|
|
108
|
-
if(!res.minutes) {
|
|
109
|
-
res.minutes = []
|
|
223
|
+
if (!res.minutes) {
|
|
224
|
+
res.minutes = [];
|
|
110
225
|
}
|
|
111
226
|
return res;
|
|
112
227
|
}
|
|
@@ -173,13 +288,15 @@ function makeSchedule(onOff, startTimes, endTime, initial = null) {
|
|
|
173
288
|
res.push(prevRecord);
|
|
174
289
|
prev = value;
|
|
175
290
|
}
|
|
176
|
-
prevRecord.countMinutes = DateTime.fromISO(i + 1 < startTimes.length ? startTimes[i+1] : endTime).diff(
|
|
291
|
+
prevRecord.countMinutes = DateTime.fromISO(i + 1 < startTimes.length ? startTimes[i + 1] : endTime).diff(
|
|
292
|
+
DateTime.fromISO(prevRecord.time),
|
|
293
|
+
"minutes"
|
|
294
|
+
).minutes;
|
|
177
295
|
}
|
|
178
296
|
return res;
|
|
179
297
|
}
|
|
180
298
|
|
|
181
299
|
function addEndToLast(priceData) {
|
|
182
|
-
|
|
183
300
|
// Add end property to the last record, that is the same as start + the difference between the last two starts, converted to ISO time
|
|
184
301
|
|
|
185
302
|
if (priceData.length > 0) {
|
|
@@ -192,10 +309,7 @@ function addEndToLast(priceData) {
|
|
|
192
309
|
}
|
|
193
310
|
|
|
194
311
|
function makeScheduleFromMinutes(minutes, initial = null) {
|
|
195
|
-
|
|
196
|
-
addEndToLast(minutes)
|
|
197
|
-
|
|
198
|
-
|
|
312
|
+
addEndToLast(minutes);
|
|
199
313
|
|
|
200
314
|
return makeSchedule(
|
|
201
315
|
minutes.map((h) => h.onOff),
|
|
@@ -276,8 +390,11 @@ function getOutputForTime(schedule, time, defaultValue) {
|
|
|
276
390
|
|
|
277
391
|
module.exports = {
|
|
278
392
|
addEndToLast,
|
|
393
|
+
addNeighbours,
|
|
279
394
|
booleanConfig,
|
|
280
395
|
calcNullSavings,
|
|
396
|
+
collapseArr,
|
|
397
|
+
expandArr,
|
|
281
398
|
countAtEnd,
|
|
282
399
|
extractPlanForDate,
|
|
283
400
|
fillArray,
|
|
@@ -298,6 +415,7 @@ module.exports = {
|
|
|
298
415
|
msgHasPriceData,
|
|
299
416
|
roundPrice,
|
|
300
417
|
saveOriginalConfig,
|
|
418
|
+
sortCollapsed,
|
|
301
419
|
sortedIndex,
|
|
302
420
|
validationFailure,
|
|
303
421
|
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
priceData: [
|
|
3
|
+
{ value: 1.1599, start: "2026-01-10T00:00:00.000+01:00" },
|
|
4
|
+
{ value: 1.1599, start: "2026-01-10T00:15:00.000+01:00" },
|
|
5
|
+
{ value: 1.1599, start: "2026-01-10T00:30:00.000+01:00" },
|
|
6
|
+
{ value: 1.1599, start: "2026-01-10T00:45:00.000+01:00" },
|
|
7
|
+
{ value: 1.1341, start: "2026-01-10T01:00:00.000+01:00" },
|
|
8
|
+
{ value: 1.1341, start: "2026-01-10T01:15:00.000+01:00" },
|
|
9
|
+
{ value: 1.1341, start: "2026-01-10T01:30:00.000+01:00" },
|
|
10
|
+
{ value: 1.1341, start: "2026-01-10T01:45:00.000+01:00" },
|
|
11
|
+
{ value: 1.1122, start: "2026-01-10T02:00:00.000+01:00" },
|
|
12
|
+
{ value: 1.1122, start: "2026-01-10T02:15:00.000+01:00" },
|
|
13
|
+
{ value: 1.1122, start: "2026-01-10T02:30:00.000+01:00" },
|
|
14
|
+
{ value: 1.1122, start: "2026-01-10T02:45:00.000+01:00" },
|
|
15
|
+
{ value: 1.0987, start: "2026-01-10T03:00:00.000+01:00" },
|
|
16
|
+
{ value: 1.0987, start: "2026-01-10T03:15:00.000+01:00" },
|
|
17
|
+
{ value: 1.0987, start: "2026-01-10T03:30:00.000+01:00" },
|
|
18
|
+
{ value: 1.0987, start: "2026-01-10T03:45:00.000+01:00" },
|
|
19
|
+
{ value: 1.1093, start: "2026-01-10T04:00:00.000+01:00" },
|
|
20
|
+
{ value: 1.1093, start: "2026-01-10T04:15:00.000+01:00" },
|
|
21
|
+
{ value: 1.1093, start: "2026-01-10T04:30:00.000+01:00" },
|
|
22
|
+
{ value: 1.1093, start: "2026-01-10T04:45:00.000+01:00" },
|
|
23
|
+
{ value: 1.1234, start: "2026-01-10T05:00:00.000+01:00" },
|
|
24
|
+
{ value: 1.1234, start: "2026-01-10T05:15:00.000+01:00" },
|
|
25
|
+
{ value: 1.1234, start: "2026-01-10T05:30:00.000+01:00" },
|
|
26
|
+
{ value: 1.1234, start: "2026-01-10T05:45:00.000+01:00" },
|
|
27
|
+
{ value: 1.1358, start: "2026-01-10T06:00:00.000+01:00" },
|
|
28
|
+
{ value: 1.1358, start: "2026-01-10T06:15:00.000+01:00" },
|
|
29
|
+
{ value: 1.1358, start: "2026-01-10T06:30:00.000+01:00" },
|
|
30
|
+
{ value: 1.1358, start: "2026-01-10T06:45:00.000+01:00" },
|
|
31
|
+
{ value: 1.1978, start: "2026-01-10T07:00:00.000+01:00" },
|
|
32
|
+
{ value: 1.1978, start: "2026-01-10T07:15:00.000+01:00" },
|
|
33
|
+
{ value: 1.1978, start: "2026-01-10T07:30:00.000+01:00" },
|
|
34
|
+
{ value: 1.1978, start: "2026-01-10T07:45:00.000+01:00" },
|
|
35
|
+
{ value: 1.2682, start: "2026-01-10T08:00:00.000+01:00" },
|
|
36
|
+
{ value: 1.2682, start: "2026-01-10T08:15:00.000+01:00" },
|
|
37
|
+
{ value: 1.2682, start: "2026-01-10T08:30:00.000+01:00" },
|
|
38
|
+
{ value: 1.2682, start: "2026-01-10T08:45:00.000+01:00" },
|
|
39
|
+
{ value: 1.3567, start: "2026-01-10T09:00:00.000+01:00" },
|
|
40
|
+
{ value: 1.3567, start: "2026-01-10T09:15:00.000+01:00" },
|
|
41
|
+
{ value: 1.3567, start: "2026-01-10T09:30:00.000+01:00" },
|
|
42
|
+
{ value: 1.3567, start: "2026-01-10T09:45:00.000+01:00" },
|
|
43
|
+
{ value: 1.4249, start: "2026-01-10T10:00:00.000+01:00" },
|
|
44
|
+
{ value: 1.4249, start: "2026-01-10T10:15:00.000+01:00" },
|
|
45
|
+
{ value: 1.4249, start: "2026-01-10T10:30:00.000+01:00" },
|
|
46
|
+
{ value: 1.4249, start: "2026-01-10T10:45:00.000+01:00" },
|
|
47
|
+
{ value: 1.4814, start: "2026-01-10T11:00:00.000+01:00" },
|
|
48
|
+
{ value: 1.4814, start: "2026-01-10T11:15:00.000+01:00" },
|
|
49
|
+
{ value: 1.4814, start: "2026-01-10T11:30:00.000+01:00" },
|
|
50
|
+
{ value: 1.4814, start: "2026-01-10T11:45:00.000+01:00" },
|
|
51
|
+
{ value: 1.4815, start: "2026-01-10T12:00:00.000+01:00" },
|
|
52
|
+
{ value: 1.4815, start: "2026-01-10T12:15:00.000+01:00" },
|
|
53
|
+
{ value: 1.4815, start: "2026-01-10T12:30:00.000+01:00" },
|
|
54
|
+
{ value: 1.4815, start: "2026-01-10T12:45:00.000+01:00" },
|
|
55
|
+
{ value: 1.5304, start: "2026-01-10T13:00:00.000+01:00" },
|
|
56
|
+
{ value: 1.5304, start: "2026-01-10T13:15:00.000+01:00" },
|
|
57
|
+
{ value: 1.5304, start: "2026-01-10T13:30:00.000+01:00" },
|
|
58
|
+
{ value: 1.5304, start: "2026-01-10T13:45:00.000+01:00" },
|
|
59
|
+
{ value: 1.6047, start: "2026-01-10T14:00:00.000+01:00" },
|
|
60
|
+
{ value: 1.6047, start: "2026-01-10T14:15:00.000+01:00" },
|
|
61
|
+
{ value: 1.6047, start: "2026-01-10T14:30:00.000+01:00" },
|
|
62
|
+
{ value: 1.6047, start: "2026-01-10T14:45:00.000+01:00" },
|
|
63
|
+
{ value: 1.703, start: "2026-01-10T15:00:00.000+01:00" },
|
|
64
|
+
{ value: 1.703, start: "2026-01-10T15:15:00.000+01:00" },
|
|
65
|
+
{ value: 1.703, start: "2026-01-10T15:30:00.000+01:00" },
|
|
66
|
+
{ value: 1.703, start: "2026-01-10T15:45:00.000+01:00" },
|
|
67
|
+
{ value: 1.8582, start: "2026-01-10T16:00:00.000+01:00" },
|
|
68
|
+
{ value: 1.8582, start: "2026-01-10T16:15:00.000+01:00" },
|
|
69
|
+
{ value: 1.8582, start: "2026-01-10T16:30:00.000+01:00" },
|
|
70
|
+
{ value: 1.8582, start: "2026-01-10T16:45:00.000+01:00" },
|
|
71
|
+
{ value: 2.0223, start: "2026-01-10T17:00:00.000+01:00" },
|
|
72
|
+
{ value: 2.0223, start: "2026-01-10T17:15:00.000+01:00" },
|
|
73
|
+
{ value: 2.0223, start: "2026-01-10T17:30:00.000+01:00" },
|
|
74
|
+
{ value: 2.0223, start: "2026-01-10T17:45:00.000+01:00" },
|
|
75
|
+
{ value: 1.985, start: "2026-01-10T18:00:00.000+01:00" },
|
|
76
|
+
{ value: 1.985, start: "2026-01-10T18:15:00.000+01:00" },
|
|
77
|
+
{ value: 1.985, start: "2026-01-10T18:30:00.000+01:00" },
|
|
78
|
+
{ value: 1.985, start: "2026-01-10T18:45:00.000+01:00" },
|
|
79
|
+
{ value: 1.8633, start: "2026-01-10T19:00:00.000+01:00" },
|
|
80
|
+
{ value: 1.8633, start: "2026-01-10T19:15:00.000+01:00" },
|
|
81
|
+
{ value: 1.8633, start: "2026-01-10T19:30:00.000+01:00" },
|
|
82
|
+
{ value: 1.8633, start: "2026-01-10T19:45:00.000+01:00" },
|
|
83
|
+
{ value: 1.7377, start: "2026-01-10T20:00:00.000+01:00" },
|
|
84
|
+
{ value: 1.7377, start: "2026-01-10T20:15:00.000+01:00" },
|
|
85
|
+
{ value: 1.7377, start: "2026-01-10T20:30:00.000+01:00" },
|
|
86
|
+
{ value: 1.7377, start: "2026-01-10T20:45:00.000+01:00" },
|
|
87
|
+
{ value: 1.6171, start: "2026-01-10T21:00:00.000+01:00" },
|
|
88
|
+
{ value: 1.6171, start: "2026-01-10T21:15:00.000+01:00" },
|
|
89
|
+
{ value: 1.6171, start: "2026-01-10T21:30:00.000+01:00" },
|
|
90
|
+
{ value: 1.6171, start: "2026-01-10T21:45:00.000+01:00" },
|
|
91
|
+
{ value: 1.4753, start: "2026-01-10T22:00:00.000+01:00" },
|
|
92
|
+
{ value: 1.4753, start: "2026-01-10T22:15:00.000+01:00" },
|
|
93
|
+
{ value: 1.4753, start: "2026-01-10T22:30:00.000+01:00" },
|
|
94
|
+
{ value: 1.4753, start: "2026-01-10T22:45:00.000+01:00" },
|
|
95
|
+
{ value: 1.328, start: "2026-01-10T23:00:00.000+01:00" },
|
|
96
|
+
{ value: 1.328, start: "2026-01-10T23:15:00.000+01:00" },
|
|
97
|
+
{ value: 1.328, start: "2026-01-10T23:30:00.000+01:00" },
|
|
98
|
+
{ value: 1.328, start: "2026-01-10T23:45:00.000+01:00" },
|
|
99
|
+
{ value: 1.2678, start: "2026-01-11T00:00:00.000+01:00" },
|
|
100
|
+
{ value: 1.2678, start: "2026-01-11T00:15:00.000+01:00" },
|
|
101
|
+
{ value: 1.2678, start: "2026-01-11T00:30:00.000+01:00" },
|
|
102
|
+
{ value: 1.2678, start: "2026-01-11T00:45:00.000+01:00" },
|
|
103
|
+
{ value: 1.2745, start: "2026-01-11T01:00:00.000+01:00" },
|
|
104
|
+
{ value: 1.2745, start: "2026-01-11T01:15:00.000+01:00" },
|
|
105
|
+
{ value: 1.2745, start: "2026-01-11T01:30:00.000+01:00" },
|
|
106
|
+
{ value: 1.2745, start: "2026-01-11T01:45:00.000+01:00" },
|
|
107
|
+
{ value: 1.1884, start: "2026-01-11T02:00:00.000+01:00" },
|
|
108
|
+
{ value: 1.1884, start: "2026-01-11T02:15:00.000+01:00" },
|
|
109
|
+
{ value: 1.1884, start: "2026-01-11T02:30:00.000+01:00" },
|
|
110
|
+
{ value: 1.1884, start: "2026-01-11T02:45:00.000+01:00" },
|
|
111
|
+
{ value: 1.1922, start: "2026-01-11T03:00:00.000+01:00" },
|
|
112
|
+
{ value: 1.1922, start: "2026-01-11T03:15:00.000+01:00" },
|
|
113
|
+
{ value: 1.1922, start: "2026-01-11T03:30:00.000+01:00" },
|
|
114
|
+
{ value: 1.1922, start: "2026-01-11T03:45:00.000+01:00" },
|
|
115
|
+
{ value: 1.1889, start: "2026-01-11T04:00:00.000+01:00" },
|
|
116
|
+
{ value: 1.1889, start: "2026-01-11T04:15:00.000+01:00" },
|
|
117
|
+
{ value: 1.1889, start: "2026-01-11T04:30:00.000+01:00" },
|
|
118
|
+
{ value: 1.1889, start: "2026-01-11T04:45:00.000+01:00" },
|
|
119
|
+
{ value: 1.2024, start: "2026-01-11T05:00:00.000+01:00" },
|
|
120
|
+
{ value: 1.2024, start: "2026-01-11T05:15:00.000+01:00" },
|
|
121
|
+
{ value: 1.2024, start: "2026-01-11T05:30:00.000+01:00" },
|
|
122
|
+
{ value: 1.2024, start: "2026-01-11T05:45:00.000+01:00" },
|
|
123
|
+
{ value: 1.1888, start: "2026-01-11T06:00:00.000+01:00" },
|
|
124
|
+
{ value: 1.1888, start: "2026-01-11T06:15:00.000+01:00" },
|
|
125
|
+
{ value: 1.1888, start: "2026-01-11T06:30:00.000+01:00" },
|
|
126
|
+
{ value: 1.1888, start: "2026-01-11T06:45:00.000+01:00" },
|
|
127
|
+
{ value: 1.2164, start: "2026-01-11T07:00:00.000+01:00" },
|
|
128
|
+
{ value: 1.2164, start: "2026-01-11T07:15:00.000+01:00" },
|
|
129
|
+
{ value: 1.2164, start: "2026-01-11T07:30:00.000+01:00" },
|
|
130
|
+
{ value: 1.2164, start: "2026-01-11T07:45:00.000+01:00" },
|
|
131
|
+
{ value: 1.2953, start: "2026-01-11T08:00:00.000+01:00" },
|
|
132
|
+
{ value: 1.2953, start: "2026-01-11T08:15:00.000+01:00" },
|
|
133
|
+
{ value: 1.2953, start: "2026-01-11T08:30:00.000+01:00" },
|
|
134
|
+
{ value: 1.2953, start: "2026-01-11T08:45:00.000+01:00" },
|
|
135
|
+
{ value: 1.4772, start: "2026-01-11T09:00:00.000+01:00" },
|
|
136
|
+
{ value: 1.4772, start: "2026-01-11T09:15:00.000+01:00" },
|
|
137
|
+
{ value: 1.4772, start: "2026-01-11T09:30:00.000+01:00" },
|
|
138
|
+
{ value: 1.4772, start: "2026-01-11T09:45:00.000+01:00" },
|
|
139
|
+
{ value: 1.467, start: "2026-01-11T10:00:00.000+01:00" },
|
|
140
|
+
{ value: 1.467, start: "2026-01-11T10:15:00.000+01:00" },
|
|
141
|
+
{ value: 1.467, start: "2026-01-11T10:30:00.000+01:00" },
|
|
142
|
+
{ value: 1.467, start: "2026-01-11T10:45:00.000+01:00" },
|
|
143
|
+
{ value: 1.4597, start: "2026-01-11T11:00:00.000+01:00" },
|
|
144
|
+
{ value: 1.4597, start: "2026-01-11T11:15:00.000+01:00" },
|
|
145
|
+
{ value: 1.4597, start: "2026-01-11T11:30:00.000+01:00" },
|
|
146
|
+
{ value: 1.4597, start: "2026-01-11T11:45:00.000+01:00" },
|
|
147
|
+
{ value: 1.4224, start: "2026-01-11T12:00:00.000+01:00" },
|
|
148
|
+
{ value: 1.4224, start: "2026-01-11T12:15:00.000+01:00" },
|
|
149
|
+
{ value: 1.4224, start: "2026-01-11T12:30:00.000+01:00" },
|
|
150
|
+
{ value: 1.4224, start: "2026-01-11T12:45:00.000+01:00" },
|
|
151
|
+
{ value: 1.4143, start: "2026-01-11T13:00:00.000+01:00" },
|
|
152
|
+
{ value: 1.4143, start: "2026-01-11T13:15:00.000+01:00" },
|
|
153
|
+
{ value: 1.4143, start: "2026-01-11T13:30:00.000+01:00" },
|
|
154
|
+
{ value: 1.4143, start: "2026-01-11T13:45:00.000+01:00" },
|
|
155
|
+
{ value: 1.448, start: "2026-01-11T14:00:00.000+01:00" },
|
|
156
|
+
{ value: 1.448, start: "2026-01-11T14:15:00.000+01:00" },
|
|
157
|
+
{ value: 1.448, start: "2026-01-11T14:30:00.000+01:00" },
|
|
158
|
+
{ value: 1.448, start: "2026-01-11T14:45:00.000+01:00" },
|
|
159
|
+
{ value: 1.4894, start: "2026-01-11T15:00:00.000+01:00" },
|
|
160
|
+
{ value: 1.4894, start: "2026-01-11T15:15:00.000+01:00" },
|
|
161
|
+
{ value: 1.4894, start: "2026-01-11T15:30:00.000+01:00" },
|
|
162
|
+
{ value: 1.4894, start: "2026-01-11T15:45:00.000+01:00" },
|
|
163
|
+
{ value: 1.6688, start: "2026-01-11T16:00:00.000+01:00" },
|
|
164
|
+
{ value: 1.6688, start: "2026-01-11T16:15:00.000+01:00" },
|
|
165
|
+
{ value: 1.6688, start: "2026-01-11T16:30:00.000+01:00" },
|
|
166
|
+
{ value: 1.6688, start: "2026-01-11T16:45:00.000+01:00" },
|
|
167
|
+
{ value: 1.7748, start: "2026-01-11T17:00:00.000+01:00" },
|
|
168
|
+
{ value: 1.7748, start: "2026-01-11T17:15:00.000+01:00" },
|
|
169
|
+
{ value: 1.7748, start: "2026-01-11T17:30:00.000+01:00" },
|
|
170
|
+
{ value: 1.7748, start: "2026-01-11T17:45:00.000+01:00" },
|
|
171
|
+
{ value: 1.7433, start: "2026-01-11T18:00:00.000+01:00" },
|
|
172
|
+
{ value: 1.7433, start: "2026-01-11T18:15:00.000+01:00" },
|
|
173
|
+
{ value: 1.7433, start: "2026-01-11T18:30:00.000+01:00" },
|
|
174
|
+
{ value: 1.7433, start: "2026-01-11T18:45:00.000+01:00" },
|
|
175
|
+
{ value: 1.8114, start: "2026-01-11T19:00:00.000+01:00" },
|
|
176
|
+
{ value: 1.8114, start: "2026-01-11T19:15:00.000+01:00" },
|
|
177
|
+
{ value: 1.8114, start: "2026-01-11T19:30:00.000+01:00" },
|
|
178
|
+
{ value: 1.8114, start: "2026-01-11T19:45:00.000+01:00" },
|
|
179
|
+
{ value: 1.5806, start: "2026-01-11T20:00:00.000+01:00" },
|
|
180
|
+
{ value: 1.5806, start: "2026-01-11T20:15:00.000+01:00" },
|
|
181
|
+
{ value: 1.5806, start: "2026-01-11T20:30:00.000+01:00" },
|
|
182
|
+
{ value: 1.5806, start: "2026-01-11T20:45:00.000+01:00" },
|
|
183
|
+
{ value: 1.5828, start: "2026-01-11T21:00:00.000+01:00" },
|
|
184
|
+
{ value: 1.5828, start: "2026-01-11T21:15:00.000+01:00" },
|
|
185
|
+
{ value: 1.5828, start: "2026-01-11T21:30:00.000+01:00" },
|
|
186
|
+
{ value: 1.5828, start: "2026-01-11T21:45:00.000+01:00" },
|
|
187
|
+
{ value: 1.3596, start: "2026-01-11T22:00:00.000+01:00" },
|
|
188
|
+
{ value: 1.3596, start: "2026-01-11T22:15:00.000+01:00" },
|
|
189
|
+
{ value: 1.3596, start: "2026-01-11T22:30:00.000+01:00" },
|
|
190
|
+
{ value: 1.3596, start: "2026-01-11T22:45:00.000+01:00" },
|
|
191
|
+
{ value: 1.2489, start: "2026-01-11T23:00:00.000+01:00" },
|
|
192
|
+
{ value: 1.2489, start: "2026-01-11T23:15:00.000+01:00" },
|
|
193
|
+
{ value: 1.2489, start: "2026-01-11T23:30:00.000+01:00" },
|
|
194
|
+
{ value: 1.2489, start: "2026-01-11T23:45:00.000+01:00", end: "2026-01-12T00:00:00.000+01:00" },
|
|
195
|
+
],
|
|
196
|
+
source: "Tibber",
|
|
197
|
+
};
|