node-red-contrib-power-saver 5.0.0-beta.2 → 5.0.0-beta.3
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/docs/changelog/README.md +5 -0
- package/package.json +1 -1
- package/src/strategy-best-save-functions.js +2 -0
- package/src/strategy-heat-capacitor-functions.js +40 -12
- package/src/strategy-heat-capacitor.js +15 -16
- package/test/data/bug-232-input.json +198 -0
- package/test/data/bug-232-output.json +1037 -0
- package/test/mostSavedStrategy.test.js +26 -0
- package/test/strategy-best-save-bug-232.test.js +53 -0
- package/test/strategy-heat-capacitor-node.test.js +4 -1
- package/test/strategy-heat-capacitor.test.js +54 -8
|
@@ -19,6 +19,32 @@ describe("mostSavedStrategy", () => {
|
|
|
19
19
|
expect(isOnOffSequencesOk(onOff, 3, 3, 100)).to.equal(false);
|
|
20
20
|
expect(isOnOffSequencesOk(onOff, 3, 3, 50)).to.equal(true);
|
|
21
21
|
expect(isOnOffSequencesOk(onOff, 3, 3, 50, 3)).to.equal(true);
|
|
22
|
+
expect(isOnOffSequencesOk(onOff, 3, 3, 100)).to.equal(false);
|
|
23
|
+
expect(isOnOffSequencesOk(onOff, 3, 3, 90)).to.equal(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("evaluates a long onOff sequence correct", () => {
|
|
27
|
+
const minutesSetting = [
|
|
28
|
+
{ count: 465, value: true },
|
|
29
|
+
{ count: 75, value: false },
|
|
30
|
+
{ count: 541, value: true },
|
|
31
|
+
{ count: 74, value: false },
|
|
32
|
+
{ count: 15, value: true },
|
|
33
|
+
{ count: 75, value: false },
|
|
34
|
+
{ count: 1335, value: true },
|
|
35
|
+
{ count: 75, value: false },
|
|
36
|
+
{ count: 315, value: true },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const minutes = [];
|
|
40
|
+
minutesSetting.forEach((ms) => {
|
|
41
|
+
for (let i = 0; i < ms.count; i++) {
|
|
42
|
+
minutes.push(ms.value);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(isOnOffSequencesOk(minutes, 75, 15, 85)).to.equal(false);
|
|
47
|
+
|
|
22
48
|
});
|
|
23
49
|
|
|
24
50
|
it("saves correct hours", () => {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const cloneDeep = require("lodash.clonedeep");
|
|
2
|
+
const { DateTime } = require("luxon");
|
|
3
|
+
const expect = require("chai").expect;
|
|
4
|
+
const helper = require("node-red-node-test-helper");
|
|
5
|
+
const bestSave = require("../src/strategy-best-save.js");
|
|
6
|
+
const input = require("./data/bug-232-input.json");
|
|
7
|
+
const output = require("./data/bug-232-output.json");
|
|
8
|
+
const { testPlan: plan, equalPlan } = require("./test-utils");
|
|
9
|
+
const { makeFlow } = require("./strategy-best-save-test-utils");
|
|
10
|
+
const { version } = require("../package.json");
|
|
11
|
+
|
|
12
|
+
helper.init(require.resolve("node-red"));
|
|
13
|
+
|
|
14
|
+
describe("ps-strategy-best-save bug-232", function () {
|
|
15
|
+
beforeEach(function (done) {
|
|
16
|
+
helper.startServer(done);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(function (done) {
|
|
20
|
+
helper.unload().then(function () {
|
|
21
|
+
helper.stopServer(done);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it.skip("find bug", function (done) {
|
|
26
|
+
const flow = makeFlow(output.config.maxMinutesOff, output.config.minMinutesOff, output.config.recoveryPercentage , output.config.recoveryMaxMinutes);
|
|
27
|
+
flow[0].minSaving = output.config.minSaving;
|
|
28
|
+
flow[0].outputIfNoSchedule = output.config.outputIfNoSchedule;
|
|
29
|
+
const expected = cloneDeep(output);
|
|
30
|
+
expected.version = output.version;
|
|
31
|
+
expected.time = output.time;
|
|
32
|
+
expected.source = output.source;
|
|
33
|
+
expected.current = false;
|
|
34
|
+
helper.load(bestSave, flow, function () {
|
|
35
|
+
const n1 = helper.getNode("n1");
|
|
36
|
+
const n2 = helper.getNode("n2");
|
|
37
|
+
n2.on("input", function (msg) {
|
|
38
|
+
expect(equalPlan(expected, msg.payload)).to.equal(true);
|
|
39
|
+
n1.warn.should.not.be.called;
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
done();
|
|
42
|
+
}, 900);
|
|
43
|
+
});
|
|
44
|
+
n1.receive({ payload: makePayload()})
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
function makePayload() {
|
|
50
|
+
const payload = cloneDeep(input);
|
|
51
|
+
payload.time = output.time;
|
|
52
|
+
return payload;
|
|
53
|
+
}
|
|
@@ -151,7 +151,10 @@ describe("ps-strategy-heat-capacitor node", function () {
|
|
|
151
151
|
const n1 = helper.getNode("n1");
|
|
152
152
|
n1.receive({ payload: multiTrade });
|
|
153
153
|
n1.receive({ payload: prices });
|
|
154
|
-
|
|
154
|
+
const latestStart = DateTime.fromISO(n1.priceData[n1.priceData.length - 1].start);
|
|
155
|
+
const cutoff = latestStart.minus({ hours: 72 });
|
|
156
|
+
const allWithinRange = n1.priceData.every((entry) => DateTime.fromISO(entry.start) >= cutoff);
|
|
157
|
+
expect(allWithinRange).to.equal(true);
|
|
155
158
|
done();
|
|
156
159
|
});
|
|
157
160
|
});
|
|
@@ -11,6 +11,51 @@ const {
|
|
|
11
11
|
const converted_prices = require("./data/converted-prices.json");
|
|
12
12
|
const decreasing_end_prices = require("./data/tibber-decreasing2-24h.json");
|
|
13
13
|
|
|
14
|
+
function buildMinutePriceVectorForTests(priceData) {
|
|
15
|
+
if (!Array.isArray(priceData) || priceData.length === 0) {
|
|
16
|
+
return { minutePrices: [], startDate: null };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const sorted = priceData
|
|
20
|
+
.slice()
|
|
21
|
+
.sort((a, b) => DateTime.fromISO(a.start).toMillis() - DateTime.fromISO(b.start).toMillis());
|
|
22
|
+
|
|
23
|
+
const minutePrices = [];
|
|
24
|
+
let previousIntervalMinutes = 60;
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
27
|
+
const currentStart = DateTime.fromISO(sorted[i].start);
|
|
28
|
+
let intervalMinutes = previousIntervalMinutes;
|
|
29
|
+
|
|
30
|
+
if (sorted[i + 1]) {
|
|
31
|
+
intervalMinutes = DateTime.fromISO(sorted[i + 1].start).diff(currentStart, "minutes").minutes;
|
|
32
|
+
} else if (sorted[i].end) {
|
|
33
|
+
intervalMinutes = DateTime.fromISO(sorted[i].end).diff(currentStart, "minutes").minutes;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
intervalMinutes = Math.max(1, Math.round(intervalMinutes));
|
|
37
|
+
previousIntervalMinutes = intervalMinutes;
|
|
38
|
+
|
|
39
|
+
for (let m = 0; m < intervalMinutes; m++) {
|
|
40
|
+
minutePrices.push(sorted[i].value);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { minutePrices, startDate: DateTime.fromISO(sorted[0].start) };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildMinutePricesFromValues(values, startIso) {
|
|
48
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const baseDate = startIso ? DateTime.fromISO(startIso) : DateTime.fromISO(converted_prices.priceData[0].start);
|
|
52
|
+
const priceData = values.map((value, idx) => ({
|
|
53
|
+
value,
|
|
54
|
+
start: baseDate.plus({ hours: idx }).toISO(),
|
|
55
|
+
}));
|
|
56
|
+
return buildMinutePriceVectorForTests(priceData).minutePrices;
|
|
57
|
+
}
|
|
58
|
+
|
|
14
59
|
describe("ps-strategy-heat-capacitor-functions", () => {
|
|
15
60
|
let prices, decreasing_24h_prices, start_date, buy_pattern, sell_pattern;
|
|
16
61
|
|
|
@@ -24,15 +69,16 @@ describe("ps-strategy-heat-capacitor-functions", () => {
|
|
|
24
69
|
const minSavings = 0.1;
|
|
25
70
|
|
|
26
71
|
before(function () {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
72
|
+
const convertedSingleHour = buildMinutePriceVectorForTests(converted_prices.priceData.slice(0, 1));
|
|
73
|
+
prices = convertedSingleHour.minutePrices;
|
|
74
|
+
decreasing_24h_prices = buildMinutePriceVectorForTests(decreasing_end_prices.priceData).minutePrices;
|
|
75
|
+
start_date = convertedSingleHour.startDate;
|
|
30
76
|
buy_pattern = Array(Math.round(timeHeat1C * maxTempAdjustment * 2)).fill(1);
|
|
31
77
|
sell_pattern = Array(Math.round(timeCool1C * maxTempAdjustment * 2)).fill(1);
|
|
32
78
|
});
|
|
33
79
|
|
|
34
80
|
it("Can calculate procurement opportunities", () => {
|
|
35
|
-
const my_prices = prices.slice(
|
|
81
|
+
const my_prices = prices.slice();
|
|
36
82
|
const my_buy_pattern = Array(5).fill(1);
|
|
37
83
|
//Calculate what it will cost to procure/sell 1 kWh as a function of time
|
|
38
84
|
let result = calculateOpportunities(my_prices, my_buy_pattern, 1);
|
|
@@ -43,7 +89,7 @@ describe("ps-strategy-heat-capacitor-functions", () => {
|
|
|
43
89
|
|
|
44
90
|
it("Can find procurement pattern", () => {
|
|
45
91
|
//Use a simple price list
|
|
46
|
-
const my_prices = [1, 2, 2, 1, 8, 1];
|
|
92
|
+
const my_prices = buildMinutePricesFromValues([1, 2, 2, 1, 8, 1], start_date.toISO());
|
|
47
93
|
|
|
48
94
|
const buy_prices = calculateOpportunities(my_prices, buy_pattern, 1);
|
|
49
95
|
const sell_prices = calculateOpportunities(my_prices, sell_pattern, 1);
|
|
@@ -57,7 +103,7 @@ describe("ps-strategy-heat-capacitor-functions", () => {
|
|
|
57
103
|
});
|
|
58
104
|
|
|
59
105
|
it("DictList test", () => {
|
|
60
|
-
const my_prices = [1, 2, 2, 1, 8, 1];
|
|
106
|
+
const my_prices = buildMinutePricesFromValues([1, 2, 2, 1, 8, 1], start_date.toISO());
|
|
61
107
|
const my_buy_sell_indexes = [
|
|
62
108
|
[0, 173],
|
|
63
109
|
[131, 251],
|
|
@@ -70,7 +116,7 @@ describe("ps-strategy-heat-capacitor-functions", () => {
|
|
|
70
116
|
});
|
|
71
117
|
|
|
72
118
|
it("DictList test at decreasing end", () => {
|
|
73
|
-
const my_prices =
|
|
119
|
+
const my_prices = decreasing_24h_prices;
|
|
74
120
|
const buy_prices = calculateOpportunities(my_prices, buy_pattern, 1);
|
|
75
121
|
const sell_prices = calculateOpportunities(my_prices, sell_pattern, 1);
|
|
76
122
|
|
|
@@ -91,7 +137,7 @@ describe("ps-strategy-heat-capacitor-functions", () => {
|
|
|
91
137
|
});
|
|
92
138
|
|
|
93
139
|
it("Check removal of low benefit buy-sell pairs", () => {
|
|
94
|
-
const my_prices = [1, 2, 1, 1.05, 1, 2];
|
|
140
|
+
const my_prices = buildMinutePricesFromValues([1, 2, 1, 1.05, 1, 2], start_date.toISO());
|
|
95
141
|
const buy_prices = calculateOpportunities(my_prices, buy_pattern, 1);
|
|
96
142
|
const sell_prices = calculateOpportunities(my_prices, sell_pattern, 1);
|
|
97
143
|
const my_buy_sell = findBestBuySellPattern(buy_prices, buy_pattern.length, sell_prices, sell_pattern.length);
|