Skip to content

Commit

Permalink
[aWATTar] move calculation logic into best price classes
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Leber <[email protected]>
  • Loading branch information
tl-photography committed Nov 15, 2024
1 parent 772026e commit f4221dc
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.time.Instant;
import java.time.ZoneId;
import java.util.Comparator;
import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
Expand All @@ -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(',');
}

// 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(",");
}
hours.append(getHourFrom(price.timerange().start(), zoneId));
second = true;
locHours.append(getHourFrom(prices.get(minIndex + i).timerange().start(), zoneId));
}
this.hours = hours.toString();

this.hours = locHours.toString();
}

@Override
Expand All @@ -61,10 +84,6 @@ public boolean contains(long timestamp) {
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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

Expand All @@ -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);
}

// 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;
members.add(member);
updateStart(member.timerange().start());
Expand Down Expand Up @@ -67,6 +84,7 @@ public String getHours() {
boolean second = false;
sort();
StringBuilder res = new StringBuilder();

for (AwattarPrice price : members) {
if (second) {
res.append(',');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@
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;
Expand Down Expand Up @@ -128,20 +126,25 @@ public void refreshChannels() {
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.");
// no prices available, can't continue
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;
Expand All @@ -151,36 +154,11 @@ public void refreshChannel(ChannelUID channelUID) {
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);
}

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;
result = new AwattarNonConsecutiveBestPriceResult(range, config.length, config.inverted, zoneId);
}

String channelId = channelUID.getIdWithoutGroup();
long diff;
switch (channelId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* 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.BeforeEach;
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;

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;
}

@BeforeEach
public void setUp() {
zoneId = ZoneId.of("GMT");
}

@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());
}
}

0 comments on commit f4221dc

Please sign in to comment.