[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 <thomas@tl-photography.at>
This commit is contained in:
tl-photography 2024-11-30 20:00:31 +01:00 committed by GitHub
parent 12c3c89f71
commit 9b803b0334
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 176 additions and 50 deletions

View File

@ -17,6 +17,7 @@ import static org.openhab.binding.awattar.internal.AwattarUtil.getHourFrom;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Comparator;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
@ -33,23 +34,45 @@ public class AwattarConsecutiveBestPriceResult extends AwattarBestPriceResult {
private final String hours;
private final ZoneId zoneId;
public AwattarConsecutiveBestPriceResult(List<AwattarPrice> prices, ZoneId zoneId) {
public AwattarConsecutiveBestPriceResult(List<AwattarPrice> prices, int length, ZoneId zoneId) {
super();
this.zoneId = zoneId;
StringBuilder hours = new StringBuilder();
boolean second = false;
for (AwattarPrice price : prices) {
// sort the prices by timerange
prices.sort(Comparator.comparing(AwattarPrice::timerange));
// calculate the range with the lowest accumulated price of length hours from the given prices
double minPrice = Double.MAX_VALUE;
int minIndex = 0;
for (int i = 0; i <= prices.size() - length; i++) {
double sum = 0;
for (int j = 0; j < length; j++) {
sum += prices.get(i + j).netPrice();
}
if (sum < minPrice) {
minPrice = sum;
minIndex = i;
}
}
// calculate the accumulated price and the range of the best price
for (int i = 0; i < length; i++) {
AwattarPrice price = prices.get(minIndex + i);
priceSum += price.netPrice();
length++;
updateStart(price.timerange().start());
updateEnd(price.timerange().end());
if (second) {
hours.append(',');
}
hours.append(getHourFrom(price.timerange().start(), zoneId));
second = true;
// create a list of hours for the best price range
StringBuilder locHours = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i > 0) {
locHours.append(",");
}
this.hours = hours.toString();
locHours.append(getHourFrom(prices.get(minIndex + i).timerange().start(), zoneId));
}
this.hours = locHours.toString();
}
@Override
@ -61,10 +84,6 @@ public class AwattarConsecutiveBestPriceResult extends AwattarBestPriceResult {
return timestamp >= getStart() && timestamp < getEnd();
}
public double getPriceSum() {
return priceSum;
}
@Override
public String toString() {
return String.format("{%s, %s, %.2f}", formatDate(getStart(), zoneId), formatDate(getEnd(), zoneId),

View File

@ -17,6 +17,7 @@ import static org.openhab.binding.awattar.internal.AwattarUtil.*;
import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@ -33,13 +34,29 @@ public class AwattarNonConsecutiveBestPriceResult extends AwattarBestPriceResult
private final ZoneId zoneId;
private boolean sorted = true;
public AwattarNonConsecutiveBestPriceResult(ZoneId zoneId) {
public AwattarNonConsecutiveBestPriceResult(List<AwattarPrice> prices, int length, boolean inverted,
ZoneId zoneId) {
super();
this.zoneId = zoneId;
members = new ArrayList<>();
prices.sort(Comparator.naturalOrder());
// sort in descending order when inverted
if (inverted) {
Collections.reverse(prices);
}
public void addMember(AwattarPrice member) {
// take up to config.length prices
for (int i = 0; i < Math.min(length, prices.size()); i++) {
addMember(prices.get(i));
}
// sort the members
members.sort(Comparator.comparing(AwattarPrice::timerange));
}
private void addMember(AwattarPrice member) {
sorted = false;
members.add(member);
updateStart(member.timerange().start());
@ -67,6 +84,7 @@ public class AwattarNonConsecutiveBestPriceResult extends AwattarBestPriceResult
boolean second = false;
sort();
StringBuilder res = new StringBuilder();
for (AwattarPrice price : members) {
if (second) {
res.append(',');

View File

@ -28,8 +28,6 @@ import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
import java.util.concurrent.ScheduledFuture;
@ -128,11 +126,13 @@ public class AwattarBestPriceHandler extends BaseThingHandler {
public void refreshChannel(ChannelUID channelUID) {
State state = UnDefType.UNDEF;
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.bridge.missing");
updateState(channelUID, state);
return;
}
AwattarBridgeHandler bridgeHandler = (AwattarBridgeHandler) bridge.getHandler();
if (bridgeHandler == null || bridgeHandler.getPrices() == null) {
logger.debug("No prices available, so can't refresh channel.");
@ -140,8 +140,11 @@ public class AwattarBestPriceHandler extends BaseThingHandler {
updateState(channelUID, state);
return;
}
ZoneId zoneId = bridgeHandler.getTimeZone();
AwattarBestPriceConfiguration config = getConfigAs(AwattarBestPriceConfiguration.class);
TimeRange timerange = getRange(config.rangeStart, config.rangeDuration, bridgeHandler.getTimeZone());
TimeRange timerange = getRange(config.rangeStart, config.rangeDuration, zoneId);
if (!(bridgeHandler.containsPriceFor(timerange))) {
updateState(channelUID, state);
return;
@ -151,36 +154,11 @@ public class AwattarBestPriceHandler extends BaseThingHandler {
List<AwattarPrice> range = getPriceRange(bridgeHandler, timerange);
if (config.consecutive) {
range.sort(Comparator.comparing(AwattarPrice::timerange));
AwattarConsecutiveBestPriceResult res = new AwattarConsecutiveBestPriceResult(
range.subList(0, config.length), bridgeHandler.getTimeZone());
for (int i = 1; i <= range.size() - config.length; i++) {
AwattarConsecutiveBestPriceResult res2 = new AwattarConsecutiveBestPriceResult(
range.subList(i, i + config.length), bridgeHandler.getTimeZone());
if (res2.getPriceSum() < res.getPriceSum()) {
res = res2;
}
}
result = res;
result = new AwattarConsecutiveBestPriceResult(range, config.length, zoneId);
} else {
range.sort(Comparator.naturalOrder());
// sort in descending order when inverted
if (config.inverted) {
Collections.reverse(range);
result = new AwattarNonConsecutiveBestPriceResult(range, config.length, config.inverted, zoneId);
}
AwattarNonConsecutiveBestPriceResult res = new AwattarNonConsecutiveBestPriceResult(
bridgeHandler.getTimeZone());
// take up to config.length prices
for (int i = 0; i < Math.min(config.length, range.size()); i++) {
res.addMember(range.get(i));
}
result = res;
}
String channelId = channelUID.getIdWithoutGroup();
long diff;
switch (channelId) {

View File

@ -0,0 +1,113 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.awattar.internal;
import static org.junit.jupiter.api.Assertions.*;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import org.junit.jupiter.api.Test;
import org.openhab.binding.awattar.internal.handler.TimeRange;
/**
* The {@link AwattarBestPriceTest} contains tests for the
* {@link AwattarConsecutiveBestPriceResult} and {@link AwattarNonConsecutiveBestPriceResult} logic.
*
* @author Thomas Leber - Initial contribution
*/
public class AwattarBestPriceTest {
private ZoneId zoneId = ZoneId.of("GMT");
public static ZonedDateTime getCalendarForHour(int hour, ZoneId zone) {
return ZonedDateTime.ofInstant(Instant.ofEpochSecond(1731283200L), zone).truncatedTo(ChronoUnit.HOURS)
.plusHours(hour);
}
public synchronized SortedSet<AwattarPrice> getPrices() {
SortedSet<AwattarPrice> prices = new TreeSet<>(Comparator.comparing(AwattarPrice::timerange));
prices.add(new AwattarPrice(103.87, 103.87, 103.87, 103.87, new TimeRange(1731283200000L, 1731286800000L)));
prices.add(new AwattarPrice(100.06, 100.06, 100.06, 100.06, new TimeRange(1731286800000L, 1731290400000L)));
prices.add(new AwattarPrice(99.06, 99.06, 99.06, 99.06, new TimeRange(1731290400000L, 1731294000000L)));
prices.add(new AwattarPrice(99.12, 99.12, 99.12, 99.12, new TimeRange(1731294000000L, 1731297600000L)));
prices.add(new AwattarPrice(105.16, 105.16, 105.16, 105.16, new TimeRange(1731297600000L, 1731301200000L)));
prices.add(new AwattarPrice(124.96, 124.96, 124.96, 124.96, new TimeRange(1731301200000L, 1731304800000L)));
prices.add(new AwattarPrice(143.91, 143.91, 143.91, 143.91, new TimeRange(1731304800000L, 1731308400000L)));
prices.add(new AwattarPrice(141.95, 141.95, 141.95, 141.95, new TimeRange(1731308400000L, 1731312000000L)));
prices.add(new AwattarPrice(135.95, 135.95, 135.95, 135.95, new TimeRange(1731312000000L, 1731315600000L)));
prices.add(new AwattarPrice(130.39, 130.39, 130.39, 130.39, new TimeRange(1731315600000L, 1731319200000L)));
prices.add(new AwattarPrice(124.5, 124.5, 124.5, 124.5, new TimeRange(1731319200000L, 1731322800000L)));
prices.add(new AwattarPrice(119.79, 119.79, 119.79, 119.79, new TimeRange(1731322800000L, 1731326400000L)));
prices.add(new AwattarPrice(131.13, 131.13, 131.13, 131.13, new TimeRange(1731326400000L, 1731330000000L)));
prices.add(new AwattarPrice(133.72, 133.72, 133.72, 133.72, new TimeRange(1731330000000L, 1731333600000L)));
prices.add(new AwattarPrice(141.58, 141.58, 141.58, 141.58, new TimeRange(1731333600000L, 1731337200000L)));
prices.add(new AwattarPrice(146.94, 146.94, 146.94, 146.94, new TimeRange(1731337200000L, 1731340800000L)));
prices.add(new AwattarPrice(150.08, 150.08, 150.08, 150.08, new TimeRange(1731340800000L, 1731344400000L)));
prices.add(new AwattarPrice(146.9, 146.9, 146.9, 146.9, new TimeRange(1731344400000L, 1731348000000L)));
prices.add(new AwattarPrice(139.87, 139.87, 139.87, 139.87, new TimeRange(1731348000000L, 1731351600000L)));
prices.add(new AwattarPrice(123.78, 123.78, 123.78, 123.78, new TimeRange(1731351600000L, 1731355200000L)));
prices.add(new AwattarPrice(119.02, 119.02, 119.02, 119.02, new TimeRange(1731355200000L, 1731358800000L)));
prices.add(new AwattarPrice(116.87, 116.87, 116.87, 116.87, new TimeRange(1731358800000L, 1731362400000L)));
prices.add(new AwattarPrice(109.72, 109.72, 109.72, 109.72, new TimeRange(1731362400000L, 1731366000000L)));
prices.add(new AwattarPrice(107.89, 107.89, 107.89, 107.89, new TimeRange(1731366000000L, 1731369600000L)));
return prices;
}
@Test
void AwattarConsecutiveBestPriceResult() {
int length = 8;
List<AwattarPrice> range = new ArrayList<>(getPrices());
range.sort(Comparator.comparing(AwattarPrice::timerange));
AwattarConsecutiveBestPriceResult result = new AwattarConsecutiveBestPriceResult(range, length, zoneId);
assertEquals("00,01,02,03,04,05,06,07", result.getHours());
}
@Test
void AwattarNonConsecutiveBestPriceResult_nonInverted() {
int length = 6;
boolean inverted = false;
List<AwattarPrice> range = new ArrayList<>(getPrices());
range.sort(Comparator.comparing(AwattarPrice::timerange));
AwattarNonConsecutiveBestPriceResult result = new AwattarNonConsecutiveBestPriceResult(range, length, inverted,
zoneId);
assertEquals("00,01,02,03,04,23", result.getHours());
}
@Test
void AwattarNonConsecutiveBestPriceResult_inverted() {
int length = 4;
boolean inverted = true;
List<AwattarPrice> range = new ArrayList<>(getPrices());
range.sort(Comparator.comparing(AwattarPrice::timerange));
AwattarNonConsecutiveBestPriceResult result = new AwattarNonConsecutiveBestPriceResult(range, length, inverted,
zoneId);
assertEquals("06,15,16,17", result.getHours());
}
}

View File

@ -45,8 +45,6 @@ import org.mockito.quality.Strictness;
import org.openhab.binding.awattar.internal.AwattarBridgeConfiguration;
import org.openhab.binding.awattar.internal.AwattarPrice;
import org.openhab.binding.awattar.internal.api.AwattarApi.AwattarApiException;
import org.openhab.binding.awattar.internal.handler.AwattarBridgeHandler;
import org.openhab.binding.awattar.internal.handler.AwattarBridgeHandlerTest;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.test.java.JavaTest;