Skip to content

Commit 9b803b0

Browse files
[aWATTar] move calculation logic into best price classes (#17729)
* [aWATTar] move calculation logic into best price classes * [aWATTar] Refactor AwattarBestPriceTest and AwattarApiTest by initializing zoneId directly and removing unnecessary setup Signed-off-by: Thomas Leber <[email protected]>
1 parent 12c3c89 commit 9b803b0

File tree

5 files changed

+175
-49
lines changed

5 files changed

+175
-49
lines changed

bundles/org.openhab.binding.awattar/src/main/java/org/openhab/binding/awattar/internal/AwattarConsecutiveBestPriceResult.java

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.time.Instant;
1919
import java.time.ZoneId;
20+
import java.util.Comparator;
2021
import java.util.List;
2122

2223
import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -33,23 +34,45 @@ public class AwattarConsecutiveBestPriceResult extends AwattarBestPriceResult {
3334
private final String hours;
3435
private final ZoneId zoneId;
3536

36-
public AwattarConsecutiveBestPriceResult(List<AwattarPrice> prices, ZoneId zoneId) {
37+
public AwattarConsecutiveBestPriceResult(List<AwattarPrice> prices, int length, ZoneId zoneId) {
3738
super();
3839
this.zoneId = zoneId;
39-
StringBuilder hours = new StringBuilder();
40-
boolean second = false;
41-
for (AwattarPrice price : prices) {
40+
41+
// sort the prices by timerange
42+
prices.sort(Comparator.comparing(AwattarPrice::timerange));
43+
44+
// calculate the range with the lowest accumulated price of length hours from the given prices
45+
double minPrice = Double.MAX_VALUE;
46+
int minIndex = 0;
47+
for (int i = 0; i <= prices.size() - length; i++) {
48+
double sum = 0;
49+
for (int j = 0; j < length; j++) {
50+
sum += prices.get(i + j).netPrice();
51+
}
52+
if (sum < minPrice) {
53+
minPrice = sum;
54+
minIndex = i;
55+
}
56+
}
57+
58+
// calculate the accumulated price and the range of the best price
59+
for (int i = 0; i < length; i++) {
60+
AwattarPrice price = prices.get(minIndex + i);
4261
priceSum += price.netPrice();
43-
length++;
4462
updateStart(price.timerange().start());
4563
updateEnd(price.timerange().end());
46-
if (second) {
47-
hours.append(',');
64+
}
65+
66+
// create a list of hours for the best price range
67+
StringBuilder locHours = new StringBuilder();
68+
for (int i = 0; i < length; i++) {
69+
if (i > 0) {
70+
locHours.append(",");
4871
}
49-
hours.append(getHourFrom(price.timerange().start(), zoneId));
50-
second = true;
72+
locHours.append(getHourFrom(prices.get(minIndex + i).timerange().start(), zoneId));
5173
}
52-
this.hours = hours.toString();
74+
75+
this.hours = locHours.toString();
5376
}
5477

5578
@Override
@@ -61,10 +84,6 @@ public boolean contains(long timestamp) {
6184
return timestamp >= getStart() && timestamp < getEnd();
6285
}
6386

64-
public double getPriceSum() {
65-
return priceSum;
66-
}
67-
6887
@Override
6988
public String toString() {
7089
return String.format("{%s, %s, %.2f}", formatDate(getStart(), zoneId), formatDate(getEnd(), zoneId),

bundles/org.openhab.binding.awattar/src/main/java/org/openhab/binding/awattar/internal/AwattarNonConsecutiveBestPriceResult.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.time.Instant;
1818
import java.time.ZoneId;
1919
import java.util.ArrayList;
20+
import java.util.Collections;
2021
import java.util.Comparator;
2122
import java.util.List;
2223

@@ -33,13 +34,29 @@ public class AwattarNonConsecutiveBestPriceResult extends AwattarBestPriceResult
3334
private final ZoneId zoneId;
3435
private boolean sorted = true;
3536

36-
public AwattarNonConsecutiveBestPriceResult(ZoneId zoneId) {
37+
public AwattarNonConsecutiveBestPriceResult(List<AwattarPrice> prices, int length, boolean inverted,
38+
ZoneId zoneId) {
3739
super();
3840
this.zoneId = zoneId;
3941
members = new ArrayList<>();
42+
43+
prices.sort(Comparator.naturalOrder());
44+
45+
// sort in descending order when inverted
46+
if (inverted) {
47+
Collections.reverse(prices);
48+
}
49+
50+
// take up to config.length prices
51+
for (int i = 0; i < Math.min(length, prices.size()); i++) {
52+
addMember(prices.get(i));
53+
}
54+
55+
// sort the members
56+
members.sort(Comparator.comparing(AwattarPrice::timerange));
4057
}
4158

42-
public void addMember(AwattarPrice member) {
59+
private void addMember(AwattarPrice member) {
4360
sorted = false;
4461
members.add(member);
4562
updateStart(member.timerange().start());
@@ -67,6 +84,7 @@ public String getHours() {
6784
boolean second = false;
6885
sort();
6986
StringBuilder res = new StringBuilder();
87+
7088
for (AwattarPrice price : members) {
7189
if (second) {
7290
res.append(',');

bundles/org.openhab.binding.awattar/src/main/java/org/openhab/binding/awattar/internal/handler/AwattarBestPriceHandler.java

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@
2828
import java.time.ZoneId;
2929
import java.time.ZonedDateTime;
3030
import java.util.ArrayList;
31-
import java.util.Collections;
32-
import java.util.Comparator;
3331
import java.util.List;
3432
import java.util.SortedSet;
3533
import java.util.concurrent.ScheduledFuture;
@@ -128,20 +126,25 @@ public void refreshChannels() {
128126
public void refreshChannel(ChannelUID channelUID) {
129127
State state = UnDefType.UNDEF;
130128
Bridge bridge = getBridge();
129+
131130
if (bridge == null) {
132131
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.bridge.missing");
133132
updateState(channelUID, state);
134133
return;
135134
}
135+
136136
AwattarBridgeHandler bridgeHandler = (AwattarBridgeHandler) bridge.getHandler();
137137
if (bridgeHandler == null || bridgeHandler.getPrices() == null) {
138138
logger.debug("No prices available, so can't refresh channel.");
139139
// no prices available, can't continue
140140
updateState(channelUID, state);
141141
return;
142142
}
143+
144+
ZoneId zoneId = bridgeHandler.getTimeZone();
145+
143146
AwattarBestPriceConfiguration config = getConfigAs(AwattarBestPriceConfiguration.class);
144-
TimeRange timerange = getRange(config.rangeStart, config.rangeDuration, bridgeHandler.getTimeZone());
147+
TimeRange timerange = getRange(config.rangeStart, config.rangeDuration, zoneId);
145148
if (!(bridgeHandler.containsPriceFor(timerange))) {
146149
updateState(channelUID, state);
147150
return;
@@ -151,36 +154,11 @@ public void refreshChannel(ChannelUID channelUID) {
151154
List<AwattarPrice> range = getPriceRange(bridgeHandler, timerange);
152155

153156
if (config.consecutive) {
154-
range.sort(Comparator.comparing(AwattarPrice::timerange));
155-
AwattarConsecutiveBestPriceResult res = new AwattarConsecutiveBestPriceResult(
156-
range.subList(0, config.length), bridgeHandler.getTimeZone());
157-
158-
for (int i = 1; i <= range.size() - config.length; i++) {
159-
AwattarConsecutiveBestPriceResult res2 = new AwattarConsecutiveBestPriceResult(
160-
range.subList(i, i + config.length), bridgeHandler.getTimeZone());
161-
if (res2.getPriceSum() < res.getPriceSum()) {
162-
res = res2;
163-
}
164-
}
165-
result = res;
157+
result = new AwattarConsecutiveBestPriceResult(range, config.length, zoneId);
166158
} else {
167-
range.sort(Comparator.naturalOrder());
168-
169-
// sort in descending order when inverted
170-
if (config.inverted) {
171-
Collections.reverse(range);
172-
}
173-
174-
AwattarNonConsecutiveBestPriceResult res = new AwattarNonConsecutiveBestPriceResult(
175-
bridgeHandler.getTimeZone());
176-
177-
// take up to config.length prices
178-
for (int i = 0; i < Math.min(config.length, range.size()); i++) {
179-
res.addMember(range.get(i));
180-
}
181-
182-
result = res;
159+
result = new AwattarNonConsecutiveBestPriceResult(range, config.length, config.inverted, zoneId);
183160
}
161+
184162
String channelId = channelUID.getIdWithoutGroup();
185163
long diff;
186164
switch (channelId) {
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* Copyright (c) 2010-2024 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
14+
package org.openhab.binding.awattar.internal;
15+
16+
import static org.junit.jupiter.api.Assertions.*;
17+
18+
import java.time.Instant;
19+
import java.time.ZoneId;
20+
import java.time.ZonedDateTime;
21+
import java.time.temporal.ChronoUnit;
22+
import java.util.ArrayList;
23+
import java.util.Comparator;
24+
import java.util.List;
25+
import java.util.SortedSet;
26+
import java.util.TreeSet;
27+
28+
import org.junit.jupiter.api.Test;
29+
import org.openhab.binding.awattar.internal.handler.TimeRange;
30+
31+
/**
32+
* The {@link AwattarBestPriceTest} contains tests for the
33+
* {@link AwattarConsecutiveBestPriceResult} and {@link AwattarNonConsecutiveBestPriceResult} logic.
34+
*
35+
* @author Thomas Leber - Initial contribution
36+
*/
37+
public class AwattarBestPriceTest {
38+
39+
private ZoneId zoneId = ZoneId.of("GMT");
40+
41+
public static ZonedDateTime getCalendarForHour(int hour, ZoneId zone) {
42+
return ZonedDateTime.ofInstant(Instant.ofEpochSecond(1731283200L), zone).truncatedTo(ChronoUnit.HOURS)
43+
.plusHours(hour);
44+
}
45+
46+
public synchronized SortedSet<AwattarPrice> getPrices() {
47+
SortedSet<AwattarPrice> prices = new TreeSet<>(Comparator.comparing(AwattarPrice::timerange));
48+
49+
prices.add(new AwattarPrice(103.87, 103.87, 103.87, 103.87, new TimeRange(1731283200000L, 1731286800000L)));
50+
prices.add(new AwattarPrice(100.06, 100.06, 100.06, 100.06, new TimeRange(1731286800000L, 1731290400000L)));
51+
prices.add(new AwattarPrice(99.06, 99.06, 99.06, 99.06, new TimeRange(1731290400000L, 1731294000000L)));
52+
prices.add(new AwattarPrice(99.12, 99.12, 99.12, 99.12, new TimeRange(1731294000000L, 1731297600000L)));
53+
prices.add(new AwattarPrice(105.16, 105.16, 105.16, 105.16, new TimeRange(1731297600000L, 1731301200000L)));
54+
prices.add(new AwattarPrice(124.96, 124.96, 124.96, 124.96, new TimeRange(1731301200000L, 1731304800000L)));
55+
prices.add(new AwattarPrice(143.91, 143.91, 143.91, 143.91, new TimeRange(1731304800000L, 1731308400000L)));
56+
prices.add(new AwattarPrice(141.95, 141.95, 141.95, 141.95, new TimeRange(1731308400000L, 1731312000000L)));
57+
prices.add(new AwattarPrice(135.95, 135.95, 135.95, 135.95, new TimeRange(1731312000000L, 1731315600000L)));
58+
prices.add(new AwattarPrice(130.39, 130.39, 130.39, 130.39, new TimeRange(1731315600000L, 1731319200000L)));
59+
prices.add(new AwattarPrice(124.5, 124.5, 124.5, 124.5, new TimeRange(1731319200000L, 1731322800000L)));
60+
prices.add(new AwattarPrice(119.79, 119.79, 119.79, 119.79, new TimeRange(1731322800000L, 1731326400000L)));
61+
prices.add(new AwattarPrice(131.13, 131.13, 131.13, 131.13, new TimeRange(1731326400000L, 1731330000000L)));
62+
prices.add(new AwattarPrice(133.72, 133.72, 133.72, 133.72, new TimeRange(1731330000000L, 1731333600000L)));
63+
prices.add(new AwattarPrice(141.58, 141.58, 141.58, 141.58, new TimeRange(1731333600000L, 1731337200000L)));
64+
prices.add(new AwattarPrice(146.94, 146.94, 146.94, 146.94, new TimeRange(1731337200000L, 1731340800000L)));
65+
prices.add(new AwattarPrice(150.08, 150.08, 150.08, 150.08, new TimeRange(1731340800000L, 1731344400000L)));
66+
prices.add(new AwattarPrice(146.9, 146.9, 146.9, 146.9, new TimeRange(1731344400000L, 1731348000000L)));
67+
prices.add(new AwattarPrice(139.87, 139.87, 139.87, 139.87, new TimeRange(1731348000000L, 1731351600000L)));
68+
prices.add(new AwattarPrice(123.78, 123.78, 123.78, 123.78, new TimeRange(1731351600000L, 1731355200000L)));
69+
prices.add(new AwattarPrice(119.02, 119.02, 119.02, 119.02, new TimeRange(1731355200000L, 1731358800000L)));
70+
prices.add(new AwattarPrice(116.87, 116.87, 116.87, 116.87, new TimeRange(1731358800000L, 1731362400000L)));
71+
prices.add(new AwattarPrice(109.72, 109.72, 109.72, 109.72, new TimeRange(1731362400000L, 1731366000000L)));
72+
prices.add(new AwattarPrice(107.89, 107.89, 107.89, 107.89, new TimeRange(1731366000000L, 1731369600000L)));
73+
74+
return prices;
75+
}
76+
77+
@Test
78+
void AwattarConsecutiveBestPriceResult() {
79+
int length = 8;
80+
81+
List<AwattarPrice> range = new ArrayList<>(getPrices());
82+
83+
range.sort(Comparator.comparing(AwattarPrice::timerange));
84+
AwattarConsecutiveBestPriceResult result = new AwattarConsecutiveBestPriceResult(range, length, zoneId);
85+
assertEquals("00,01,02,03,04,05,06,07", result.getHours());
86+
}
87+
88+
@Test
89+
void AwattarNonConsecutiveBestPriceResult_nonInverted() {
90+
int length = 6;
91+
boolean inverted = false;
92+
93+
List<AwattarPrice> range = new ArrayList<>(getPrices());
94+
95+
range.sort(Comparator.comparing(AwattarPrice::timerange));
96+
AwattarNonConsecutiveBestPriceResult result = new AwattarNonConsecutiveBestPriceResult(range, length, inverted,
97+
zoneId);
98+
assertEquals("00,01,02,03,04,23", result.getHours());
99+
}
100+
101+
@Test
102+
void AwattarNonConsecutiveBestPriceResult_inverted() {
103+
int length = 4;
104+
boolean inverted = true;
105+
106+
List<AwattarPrice> range = new ArrayList<>(getPrices());
107+
108+
range.sort(Comparator.comparing(AwattarPrice::timerange));
109+
AwattarNonConsecutiveBestPriceResult result = new AwattarNonConsecutiveBestPriceResult(range, length, inverted,
110+
zoneId);
111+
assertEquals("06,15,16,17", result.getHours());
112+
}
113+
}

bundles/org.openhab.binding.awattar/src/test/java/org/openhab/binding/awattar/internal/api/AwattarApiTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@
4545
import org.openhab.binding.awattar.internal.AwattarBridgeConfiguration;
4646
import org.openhab.binding.awattar.internal.AwattarPrice;
4747
import org.openhab.binding.awattar.internal.api.AwattarApi.AwattarApiException;
48-
import org.openhab.binding.awattar.internal.handler.AwattarBridgeHandler;
49-
import org.openhab.binding.awattar.internal.handler.AwattarBridgeHandlerTest;
5048
import org.openhab.core.i18n.TimeZoneProvider;
5149
import org.openhab.core.test.java.JavaTest;
5250

0 commit comments

Comments
 (0)