mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[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:
parent
12c3c89f71
commit
9b803b0334
@ -17,6 +17,7 @@ import static org.openhab.binding.awattar.internal.AwattarUtil.getHourFrom;
|
|||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
@ -33,23 +34,45 @@ public class AwattarConsecutiveBestPriceResult extends AwattarBestPriceResult {
|
|||||||
private final String hours;
|
private final String hours;
|
||||||
private final ZoneId zoneId;
|
private final ZoneId zoneId;
|
||||||
|
|
||||||
public AwattarConsecutiveBestPriceResult(List<AwattarPrice> prices, ZoneId zoneId) {
|
public AwattarConsecutiveBestPriceResult(List<AwattarPrice> prices, int length, ZoneId zoneId) {
|
||||||
super();
|
super();
|
||||||
this.zoneId = zoneId;
|
this.zoneId = zoneId;
|
||||||
StringBuilder hours = new StringBuilder();
|
|
||||||
boolean second = false;
|
// sort the prices by timerange
|
||||||
for (AwattarPrice price : prices) {
|
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();
|
priceSum += price.netPrice();
|
||||||
length++;
|
|
||||||
updateStart(price.timerange().start());
|
updateStart(price.timerange().start());
|
||||||
updateEnd(price.timerange().end());
|
updateEnd(price.timerange().end());
|
||||||
if (second) {
|
|
||||||
hours.append(',');
|
|
||||||
}
|
|
||||||
hours.append(getHourFrom(price.timerange().start(), zoneId));
|
|
||||||
second = true;
|
|
||||||
}
|
}
|
||||||
this.hours = hours.toString();
|
|
||||||
|
// 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(",");
|
||||||
|
}
|
||||||
|
locHours.append(getHourFrom(prices.get(minIndex + i).timerange().start(), zoneId));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hours = locHours.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -61,10 +84,6 @@ public class AwattarConsecutiveBestPriceResult extends AwattarBestPriceResult {
|
|||||||
return timestamp >= getStart() && timestamp < getEnd();
|
return timestamp >= getStart() && timestamp < getEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getPriceSum() {
|
|
||||||
return priceSum;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("{%s, %s, %.2f}", formatDate(getStart(), zoneId), formatDate(getEnd(), zoneId),
|
return String.format("{%s, %s, %.2f}", formatDate(getStart(), zoneId), formatDate(getEnd(), zoneId),
|
||||||
|
@ -17,6 +17,7 @@ import static org.openhab.binding.awattar.internal.AwattarUtil.*;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -33,13 +34,29 @@ public class AwattarNonConsecutiveBestPriceResult extends AwattarBestPriceResult
|
|||||||
private final ZoneId zoneId;
|
private final ZoneId zoneId;
|
||||||
private boolean sorted = true;
|
private boolean sorted = true;
|
||||||
|
|
||||||
public AwattarNonConsecutiveBestPriceResult(ZoneId zoneId) {
|
public AwattarNonConsecutiveBestPriceResult(List<AwattarPrice> prices, int length, boolean inverted,
|
||||||
|
ZoneId zoneId) {
|
||||||
super();
|
super();
|
||||||
this.zoneId = zoneId;
|
this.zoneId = zoneId;
|
||||||
members = new ArrayList<>();
|
members = new ArrayList<>();
|
||||||
|
|
||||||
|
prices.sort(Comparator.naturalOrder());
|
||||||
|
|
||||||
|
// sort in descending order when inverted
|
||||||
|
if (inverted) {
|
||||||
|
Collections.reverse(prices);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addMember(AwattarPrice member) {
|
private void addMember(AwattarPrice member) {
|
||||||
sorted = false;
|
sorted = false;
|
||||||
members.add(member);
|
members.add(member);
|
||||||
updateStart(member.timerange().start());
|
updateStart(member.timerange().start());
|
||||||
@ -67,6 +84,7 @@ public class AwattarNonConsecutiveBestPriceResult extends AwattarBestPriceResult
|
|||||||
boolean second = false;
|
boolean second = false;
|
||||||
sort();
|
sort();
|
||||||
StringBuilder res = new StringBuilder();
|
StringBuilder res = new StringBuilder();
|
||||||
|
|
||||||
for (AwattarPrice price : members) {
|
for (AwattarPrice price : members) {
|
||||||
if (second) {
|
if (second) {
|
||||||
res.append(',');
|
res.append(',');
|
||||||
|
@ -28,8 +28,6 @@ import java.time.Instant;
|
|||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
@ -128,11 +126,13 @@ public class AwattarBestPriceHandler extends BaseThingHandler {
|
|||||||
public void refreshChannel(ChannelUID channelUID) {
|
public void refreshChannel(ChannelUID channelUID) {
|
||||||
State state = UnDefType.UNDEF;
|
State state = UnDefType.UNDEF;
|
||||||
Bridge bridge = getBridge();
|
Bridge bridge = getBridge();
|
||||||
|
|
||||||
if (bridge == null) {
|
if (bridge == null) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.bridge.missing");
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.bridge.missing");
|
||||||
updateState(channelUID, state);
|
updateState(channelUID, state);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AwattarBridgeHandler bridgeHandler = (AwattarBridgeHandler) bridge.getHandler();
|
AwattarBridgeHandler bridgeHandler = (AwattarBridgeHandler) bridge.getHandler();
|
||||||
if (bridgeHandler == null || bridgeHandler.getPrices() == null) {
|
if (bridgeHandler == null || bridgeHandler.getPrices() == null) {
|
||||||
logger.debug("No prices available, so can't refresh channel.");
|
logger.debug("No prices available, so can't refresh channel.");
|
||||||
@ -140,8 +140,11 @@ public class AwattarBestPriceHandler extends BaseThingHandler {
|
|||||||
updateState(channelUID, state);
|
updateState(channelUID, state);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ZoneId zoneId = bridgeHandler.getTimeZone();
|
||||||
|
|
||||||
AwattarBestPriceConfiguration config = getConfigAs(AwattarBestPriceConfiguration.class);
|
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))) {
|
if (!(bridgeHandler.containsPriceFor(timerange))) {
|
||||||
updateState(channelUID, state);
|
updateState(channelUID, state);
|
||||||
return;
|
return;
|
||||||
@ -151,36 +154,11 @@ public class AwattarBestPriceHandler extends BaseThingHandler {
|
|||||||
List<AwattarPrice> range = getPriceRange(bridgeHandler, timerange);
|
List<AwattarPrice> range = getPriceRange(bridgeHandler, timerange);
|
||||||
|
|
||||||
if (config.consecutive) {
|
if (config.consecutive) {
|
||||||
range.sort(Comparator.comparing(AwattarPrice::timerange));
|
result = new AwattarConsecutiveBestPriceResult(range, config.length, zoneId);
|
||||||
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;
|
|
||||||
} else {
|
} else {
|
||||||
range.sort(Comparator.naturalOrder());
|
result = new AwattarNonConsecutiveBestPriceResult(range, config.length, config.inverted, zoneId);
|
||||||
|
|
||||||
// sort in descending order when inverted
|
|
||||||
if (config.inverted) {
|
|
||||||
Collections.reverse(range);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
String channelId = channelUID.getIdWithoutGroup();
|
||||||
long diff;
|
long diff;
|
||||||
switch (channelId) {
|
switch (channelId) {
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -45,8 +45,6 @@ import org.mockito.quality.Strictness;
|
|||||||
import org.openhab.binding.awattar.internal.AwattarBridgeConfiguration;
|
import org.openhab.binding.awattar.internal.AwattarBridgeConfiguration;
|
||||||
import org.openhab.binding.awattar.internal.AwattarPrice;
|
import org.openhab.binding.awattar.internal.AwattarPrice;
|
||||||
import org.openhab.binding.awattar.internal.api.AwattarApi.AwattarApiException;
|
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.i18n.TimeZoneProvider;
|
||||||
import org.openhab.core.test.java.JavaTest;
|
import org.openhab.core.test.java.JavaTest;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user