[smhi] Fix exception in aggregation function when daily forecast is empty (#10851)

* Fix exception in aggregation function when daily forecast is empty

Also catch any Exception to prevent thread from crashing

Signed-off-by: Anders Alfredsson <andersb86@gmail.com>

* Refactor to improve robustness of calculations

Improve tests to cover different forecast scenarios

Signed-off-by: Anders Alfredsson <andersb86@gmail.com>
pull/10873/head
Anders Alfredsson 2021-06-15 23:56:50 +02:00 committed by GitHub
parent c21c468cac
commit c9933454db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 338 additions and 132 deletions

View File

@ -13,7 +13,8 @@
package org.openhab.binding.smhi.internal; package org.openhab.binding.smhi.internal;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -24,23 +25,73 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
*/ */
@NonNullByDefault @NonNullByDefault
public class ForecastAggregator { public class ForecastAggregator {
/**
* Get the maximum value for the specified parameter for the n:th day after the forecast's reference time
*
* @param timeSeries
* @param dayOffset
* @param parameter
* @return
*/
public static Optional<BigDecimal> max(TimeSeries timeSeries, int dayOffset, String parameter) { public static Optional<BigDecimal> max(TimeSeries timeSeries, int dayOffset, String parameter) {
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset); List<Forecast> dayForecasts = timeSeries.getDay(dayOffset);
return dayForecasts.stream().map(forecast -> forecast.getParameter(parameter)).filter(Optional::isPresent) return dayForecasts.stream().map(forecast -> forecast.getParameter(parameter)).filter(Optional::isPresent)
.map(Optional::get).max(BigDecimal::compareTo); .map(Optional::get).max(BigDecimal::compareTo);
} }
/**
* Get the minimum value for the specified parameter for the n:th day after the forecast's reference time
*
* @param timeSeries
* @param dayOffset
* @param parameter
* @return
*/
public static Optional<BigDecimal> min(TimeSeries timeSeries, int dayOffset, String parameter) { public static Optional<BigDecimal> min(TimeSeries timeSeries, int dayOffset, String parameter) {
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset); List<Forecast> dayForecasts = timeSeries.getDay(dayOffset);
return dayForecasts.stream().map(forecast -> forecast.getParameter(parameter)).filter(Optional::isPresent) return dayForecasts.stream().map(forecast -> forecast.getParameter(parameter)).filter(Optional::isPresent)
.map(Optional::get).min(BigDecimal::compareTo); .map(Optional::get).min(BigDecimal::compareTo);
} }
/**
* Get the total value for the specified parameter for the n:th day after the forecast's reference time.
* If there aren't any values for every hour, the previous value is used for each empty slot.
*
* @param timeSeries
* @param dayOffset
* @param parameter
* @return
*/
public static Optional<BigDecimal> total(TimeSeries timeSeries, int dayOffset, String parameter) { public static Optional<BigDecimal> total(TimeSeries timeSeries, int dayOffset, String parameter) {
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset); List<Forecast> dayForecasts = timeSeries.getDay(dayOffset);
BigDecimal sum = dayForecasts.stream().map(forecast -> forecast.getParameter(parameter)) List<BigDecimal> values = new ArrayList<>();
.filter(Optional::isPresent).map(Optional::get).reduce(BigDecimal::add).orElse(BigDecimal.ZERO); for (int i = 0; i < dayForecasts.size(); i++) {
BigDecimal mean = sum.divide(BigDecimal.valueOf(dayForecasts.size()), RoundingMode.HALF_UP); Forecast current = dayForecasts.get(i);
return Optional.of(mean.multiply(BigDecimal.valueOf(24))); long hours;
if (i == 0) {
hours = current.getValidTime().until(dayForecasts.get(i + 1).getValidTime(), ChronoUnit.HOURS);
} else {
hours = dayForecasts.get(i - 1).getValidTime().until(current.getValidTime(), ChronoUnit.HOURS);
}
values.add(current.getParameter(parameter).map(value -> value.multiply(BigDecimal.valueOf(hours)))
.orElse(BigDecimal.ZERO));
}
return values.stream().reduce(BigDecimal::add);
}
/**
* Get the value at 12:00 UTC for the specified parameter for the n:th day after the forecast's reference time.
* If that time is not included (should only happen for day 0 if after 12:00), get the first value for the day
* instead.
*
* @param timeSeries
* @param dayOffset
* @param parameter
* @return
*/
public static Optional<BigDecimal> noonOrFirst(TimeSeries timeSeries, int dayOffset, String parameter) {
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset);
return dayForecasts.stream().filter(forecast -> forecast.getValidTime().getHour() >= 12).findFirst()
.flatMap(forecast -> forecast.getParameter(parameter));
} }
} }

View File

@ -63,7 +63,6 @@ public class SmhiHandler extends BaseThingHandler {
private final HttpClient httpClient; private final HttpClient httpClient;
private @Nullable SmhiConnector connection; private @Nullable SmhiConnector connection;
private ZonedDateTime currentHour; private ZonedDateTime currentHour;
private ZonedDateTime currentDay;
private @Nullable TimeSeries cachedTimeSeries; private @Nullable TimeSeries cachedTimeSeries;
private boolean hasLatestForecast = false; private boolean hasLatestForecast = false;
private @Nullable Future<?> forecastUpdater; private @Nullable Future<?> forecastUpdater;
@ -73,7 +72,6 @@ public class SmhiHandler extends BaseThingHandler {
super(thing); super(thing);
this.httpClient = httpClient; this.httpClient = httpClient;
this.currentHour = calculateCurrentHour(); this.currentHour = calculateCurrentHour();
this.currentDay = calculateCurrentDay();
} }
/** /**
@ -142,7 +140,7 @@ public class SmhiHandler extends BaseThingHandler {
if (channels.isEmpty()) { if (channels.isEmpty()) {
continue; continue;
} }
Optional<Forecast> forecast = timeSeries.getForecast(i); Optional<Forecast> forecast = timeSeries.getForecast(currentHour, i);
if (forecast.isPresent()) { if (forecast.isPresent()) {
channels.forEach(c -> { channels.forEach(c -> {
String id = c.getUID().getIdWithoutGroup(); String id = c.getUID().getIdWithoutGroup();
@ -159,30 +157,12 @@ public class SmhiHandler extends BaseThingHandler {
} }
int dayOffset = i; int dayOffset = i;
int hourOffset = 24 * dayOffset + 12;
Optional<Forecast> forecast = timeSeries.getForecast(currentDay, hourOffset);
if (forecast.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug("No forecast yet for {}", currentDay.plusHours(hourOffset));
}
channels.forEach(c -> {
updateState(c.getUID(), UnDefType.UNDEF);
});
} else {
channels.forEach(c -> { channels.forEach(c -> {
String id = c.getUID().getIdWithoutGroup(); String id = c.getUID().getIdWithoutGroup();
Optional<BigDecimal> value; updateChannel(c, getDayValue(id, timeSeries, dayOffset));
if (isAggregatedChannel(id)) {
value = getAggregatedValue(id, timeSeries, dayOffset);
} else {
value = forecast.get().getParameter(id);
}
updateChannel(c, value);
}); });
} }
} }
}
private void updateChannel(Channel channel, Optional<BigDecimal> value) { private void updateChannel(Channel channel, Optional<BigDecimal> value) {
String id = channel.getUID().getIdWithoutGroup(); String id = channel.getUID().getIdWithoutGroup();
@ -258,9 +238,9 @@ public class SmhiHandler extends BaseThingHandler {
* published, in that case, fetch it and update channels. * published, in that case, fetch it and update channels.
*/ */
private void waitForForecast() { private void waitForForecast() {
try {
if (isItNewHour()) { if (isItNewHour()) {
currentHour = calculateCurrentHour(); currentHour = calculateCurrentHour();
currentDay = calculateCurrentDay();
// Update channels with cached forecasts - just shift an hour forward // Update channels with cached forecasts - just shift an hour forward
TimeSeries forecast = cachedTimeSeries; TimeSeries forecast = cachedTimeSeries;
if (forecast != null) { if (forecast != null) {
@ -271,6 +251,10 @@ public class SmhiHandler extends BaseThingHandler {
if (!hasLatestForecast && isForecastUpdated()) { if (!hasLatestForecast && isForecastUpdated()) {
getUpdatedForecast(); getUpdatedForecast();
} }
} catch (RuntimeException e) {
logger.warn("Unexpected exception occurred, please report to the developers: {}: {}", e.getClass(),
e.getMessage());
}
} }
/** /**
@ -325,7 +309,7 @@ public class SmhiHandler extends BaseThingHandler {
try { try {
forecast = apiConnection.getForecast(config.latitude, config.longitude); forecast = apiConnection.getForecast(config.latitude, config.longitude);
} catch (SmhiException e) { } catch (SmhiException e) {
String message = e.getCause() == null ? e.getMessage() : e.getCause().getMessage(); String message = Optional.ofNullable(e.getCause()).orElse(e).getMessage();
logger.debug("Failed to get new forecast: {}", message); logger.debug("Failed to get new forecast: {}", message);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
return; return;
@ -359,23 +343,10 @@ public class SmhiHandler extends BaseThingHandler {
return ZonedDateTime.of(y, m, d, h, 0, 0, 0, ZoneOffset.UTC); return ZonedDateTime.of(y, m, d, h, 0, 0, 0, ZoneOffset.UTC);
} }
/**
* Get the current time rounded down to day
*
* @return A {@link ZonedDateTime} corresponding to the last even day.
*/
private ZonedDateTime calculateCurrentDay() {
ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC);
int y = now.getYear();
int m = now.getMonth().getValue();
int d = now.getDayOfMonth();
return ZonedDateTime.of(y, m, d, 0, 0, 0, 0, ZoneOffset.UTC);
}
/** /**
* Creates channels based on selections in thing configuration * Creates channels based on selections in thing configuration
* *
* @return * @return A List of Channels to add to the Thing
*/ */
private List<Channel> createChannels() { private List<Channel> createChannels() {
List<Channel> channels = new ArrayList<>(); List<Channel> channels = new ArrayList<>();
@ -430,6 +401,7 @@ public class SmhiHandler extends BaseThingHandler {
break; break;
case WIND_DIRECTION: case WIND_DIRECTION:
itemType += ":Angle"; itemType += ":Angle";
break;
case WIND_SPEED: case WIND_SPEED:
case WIND_MAX: case WIND_MAX:
case WIND_MIN: case WIND_MIN:
@ -456,21 +428,16 @@ public class SmhiHandler extends BaseThingHandler {
return channel; return channel;
} }
private boolean isAggregatedChannel(String channelId) { /**
switch (channelId) { * Gets the value that represents the day forecast for a specified day and channel
case TEMPERATURE_MAX: *
case TEMPERATURE_MIN: * @param parameter The parameter to retrieve or calculate
case WIND_MAX: * @param timeSeries A TimeSeries object containing forecasts
case WIND_MIN: * @param dayOffset The number of days from the start of the TimeSeries
case PRECIPITATION_TOTAL: * @return An Optional containing the retrieved or calculated value
return true; */
default: private Optional<BigDecimal> getDayValue(String parameter, TimeSeries timeSeries, int dayOffset) {
return false; switch (parameter) {
}
}
private Optional<BigDecimal> getAggregatedValue(String channelId, TimeSeries timeSeries, int dayOffset) {
switch (channelId) {
case TEMPERATURE_MAX: case TEMPERATURE_MAX:
return ForecastAggregator.max(timeSeries, dayOffset, TEMPERATURE); return ForecastAggregator.max(timeSeries, dayOffset, TEMPERATURE);
case TEMPERATURE_MIN: case TEMPERATURE_MIN:
@ -482,7 +449,7 @@ public class SmhiHandler extends BaseThingHandler {
case PRECIPITATION_TOTAL: case PRECIPITATION_TOTAL:
return ForecastAggregator.total(timeSeries, dayOffset, PRECIPITATION_MEAN); return ForecastAggregator.total(timeSeries, dayOffset, PRECIPITATION_MEAN);
default: default:
return Optional.empty(); return ForecastAggregator.noonOrFirst(timeSeries, dayOffset, parameter);
} }
} }
} }

View File

@ -72,8 +72,14 @@ public class TimeSeries implements Iterable<Forecast> {
return Optional.empty(); return Optional.empty();
} }
/**
* Get all Forecasts for the n:th day after the start of the TimeSeries
*
* @param dayOffset
* @return
*/
public List<Forecast> getDay(int dayOffset) { public List<Forecast> getDay(int dayOffset) {
ZonedDateTime day = referenceTime.plusDays(dayOffset); ZonedDateTime day = referenceTime.plusDays(dayOffset).plusHours(1);
return forecasts.stream().filter(forecast -> forecast.getValidTime().getDayOfMonth() == day.getDayOfMonth()) return forecasts.stream().filter(forecast -> forecast.getValidTime().getDayOfMonth() == day.getDayOfMonth())
.collect(Collectors.toList()); .collect(Collectors.toList());
} }

View File

@ -21,6 +21,7 @@ import java.math.BigDecimal;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -29,18 +30,29 @@ import org.junit.jupiter.api.Test;
*/ */
@NonNullByDefault @NonNullByDefault
public class SmhiTest { public class SmhiTest {
private static final ZonedDateTime TIME = ZonedDateTime.parse("2020-12-13T08:15:00Z"); private static final ZonedDateTime TIME = ZonedDateTime.parse("2021-06-13T07:00:00Z");
private @NonNullByDefault({}) TimeSeries timeSeries; private @NonNullByDefault({}) TimeSeries timeSeries1;
private @NonNullByDefault({}) TimeSeries timeSeries2;
private @NonNullByDefault({}) TimeSeries timeSeries3;
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
try { try {
InputStream is = SmhiTest.class.getResourceAsStream("forecast.json"); @Nullable
if (is == null) { InputStream is1 = SmhiTest.class.getResourceAsStream("forecast1.json");
@Nullable
InputStream is2 = SmhiTest.class.getResourceAsStream("forecast2.json");
@Nullable
InputStream is3 = SmhiTest.class.getResourceAsStream("forecast3.json");
if (is1 == null || is2 == null || is3 == null) {
throw new AssertionError("Couldn't read forecast example"); throw new AssertionError("Couldn't read forecast example");
} }
String jsonString = new String(is.readAllBytes()); String str1 = new String(is1.readAllBytes());
timeSeries = Parser.parseTimeSeries(jsonString); String str2 = new String(is2.readAllBytes());
String str3 = new String(is3.readAllBytes());
timeSeries1 = Parser.parseTimeSeries(str1);
timeSeries2 = Parser.parseTimeSeries(str2);
timeSeries3 = Parser.parseTimeSeries(str3);
} catch (IOException e) { } catch (IOException e) {
throw new AssertionError("Couldn't read forecast example"); throw new AssertionError("Couldn't read forecast example");
} }
@ -48,64 +60,232 @@ public class SmhiTest {
@Test @Test
public void parameterTest() { public void parameterTest() {
assertNotNull(timeSeries); assertNotNull(timeSeries1);
Forecast forecast = timeSeries.getForecast(TIME, 0).orElseThrow(AssertionError::new); Forecast forecast0 = timeSeries1.getForecast(TIME, 0).orElseThrow(AssertionError::new);
BigDecimal msl = forecast.getParameter(PRESSURE).orElseThrow(AssertionError::new); BigDecimal msl0 = forecast0.getParameter(PRESSURE).orElseThrow(AssertionError::new);
BigDecimal t = forecast.getParameter(TEMPERATURE).orElseThrow(AssertionError::new); BigDecimal t0 = forecast0.getParameter(TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal vis = forecast.getParameter(VISIBILITY).orElseThrow(AssertionError::new); BigDecimal vis0 = forecast0.getParameter(VISIBILITY).orElseThrow(AssertionError::new);
BigDecimal wd = forecast.getParameter(WIND_DIRECTION).orElseThrow(AssertionError::new); BigDecimal wd0 = forecast0.getParameter(WIND_DIRECTION).orElseThrow(AssertionError::new);
BigDecimal ws = forecast.getParameter(WIND_SPEED).orElseThrow(AssertionError::new); BigDecimal ws0 = forecast0.getParameter(WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal r = forecast.getParameter(RELATIVE_HUMIDITY).orElseThrow(AssertionError::new); BigDecimal r0 = forecast0.getParameter(RELATIVE_HUMIDITY).orElseThrow(AssertionError::new);
BigDecimal tstm = forecast.getParameter(THUNDER_PROBABILITY).orElseThrow(AssertionError::new); BigDecimal tstm0 = forecast0.getParameter(THUNDER_PROBABILITY).orElseThrow(AssertionError::new);
BigDecimal tcc = forecast.getParameter(TOTAL_CLOUD_COVER).orElseThrow(AssertionError::new); BigDecimal tcc0 = forecast0.getParameter(TOTAL_CLOUD_COVER).orElseThrow(AssertionError::new);
BigDecimal lcc = forecast.getParameter(LOW_CLOUD_COVER).orElseThrow(AssertionError::new); BigDecimal lcc0 = forecast0.getParameter(LOW_CLOUD_COVER).orElseThrow(AssertionError::new);
BigDecimal mcc = forecast.getParameter(MEDIUM_CLOUD_COVER).orElseThrow(AssertionError::new); BigDecimal mcc0 = forecast0.getParameter(MEDIUM_CLOUD_COVER).orElseThrow(AssertionError::new);
BigDecimal hcc = forecast.getParameter(HIGH_CLOUD_COVER).orElseThrow(AssertionError::new); BigDecimal hcc0 = forecast0.getParameter(HIGH_CLOUD_COVER).orElseThrow(AssertionError::new);
BigDecimal gust = forecast.getParameter(GUST).orElseThrow(AssertionError::new); BigDecimal gust0 = forecast0.getParameter(GUST).orElseThrow(AssertionError::new);
BigDecimal pmin = forecast.getParameter(PRECIPITATION_MIN).orElseThrow(AssertionError::new); BigDecimal pmin0 = forecast0.getParameter(PRECIPITATION_MIN).orElseThrow(AssertionError::new);
BigDecimal pmax = forecast.getParameter(PRECIPITATION_MAX).orElseThrow(AssertionError::new); BigDecimal pmax0 = forecast0.getParameter(PRECIPITATION_MAX).orElseThrow(AssertionError::new);
BigDecimal spp = forecast.getParameter(PERCENT_FROZEN).orElseThrow(AssertionError::new); BigDecimal spp0 = forecast0.getParameter(PERCENT_FROZEN).orElseThrow(AssertionError::new);
BigDecimal pcat = forecast.getParameter(PRECIPITATION_CATEGORY).orElseThrow(AssertionError::new); BigDecimal pcat0 = forecast0.getParameter(PRECIPITATION_CATEGORY).orElseThrow(AssertionError::new);
BigDecimal pmean = forecast.getParameter(PRECIPITATION_MEAN).orElseThrow(AssertionError::new); BigDecimal pmean0 = forecast0.getParameter(PRECIPITATION_MEAN).orElseThrow(AssertionError::new);
BigDecimal pmedian = forecast.getParameter(PRECIPITATION_MEDIAN).orElseThrow(AssertionError::new); BigDecimal pmedian0 = forecast0.getParameter(PRECIPITATION_MEDIAN).orElseThrow(AssertionError::new);
BigDecimal wsymb = forecast.getParameter(WEATHER_SYMBOL).orElseThrow(AssertionError::new); BigDecimal wsymb0 = forecast0.getParameter(WEATHER_SYMBOL).orElseThrow(AssertionError::new);
assertEquals(0, msl.compareTo(BigDecimal.valueOf(1013.7))); assertEquals(0, msl0.compareTo(BigDecimal.valueOf(1014.1)));
assertEquals(0, t.compareTo(BigDecimal.valueOf(3.0))); assertEquals(0, t0.compareTo(BigDecimal.valueOf(18.2)));
assertEquals(0, vis.compareTo(BigDecimal.valueOf(24.3))); assertEquals(0, vis0.compareTo(BigDecimal.valueOf(28.4)));
assertEquals(0, wd.compareTo(BigDecimal.valueOf(110))); assertEquals(0, wd0.compareTo(BigDecimal.valueOf(313)));
assertEquals(0, ws.compareTo(BigDecimal.valueOf(1.5))); assertEquals(0, ws0.compareTo(BigDecimal.valueOf(2)));
assertEquals(0, r.compareTo(BigDecimal.valueOf(96))); assertEquals(0, r0.compareTo(BigDecimal.valueOf(52)));
assertEquals(0, tstm.compareTo(BigDecimal.valueOf(0))); assertEquals(0, tstm0.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, tcc.compareTo(BigDecimal.valueOf(8))); assertEquals(0, tcc0.compareTo(BigDecimal.valueOf(3)));
assertEquals(0, lcc.compareTo(BigDecimal.valueOf(8))); assertEquals(0, lcc0.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, mcc.compareTo(BigDecimal.valueOf(4))); assertEquals(0, mcc0.compareTo(BigDecimal.valueOf(2)));
assertEquals(0, hcc.compareTo(BigDecimal.valueOf(0))); assertEquals(0, hcc0.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, gust.compareTo(BigDecimal.valueOf(3.0))); assertEquals(0, gust0.compareTo(BigDecimal.valueOf(6.9)));
assertEquals(0, pmin.compareTo(BigDecimal.valueOf(0.0))); assertEquals(0, pmin0.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, pmax.compareTo(BigDecimal.valueOf(0.0))); assertEquals(0, pmax0.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, spp.compareTo(BigDecimal.valueOf(-9))); assertEquals(0, spp0.compareTo(BigDecimal.valueOf(-9)));
assertEquals(0, pcat.compareTo(BigDecimal.valueOf(0))); assertEquals(0, pcat0.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, pmean.compareTo(BigDecimal.valueOf(0.0))); assertEquals(0, pmean0.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, pmedian.compareTo(BigDecimal.valueOf(0.0))); assertEquals(0, pmedian0.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, wsymb.compareTo(BigDecimal.valueOf(6))); assertEquals(0, wsymb0.compareTo(BigDecimal.valueOf(2)));
Forecast forecast1 = timeSeries1.getForecast(TIME.plusHours(1), 0).orElseThrow(AssertionError::new);
BigDecimal msl1 = forecast1.getParameter(PRESSURE).orElseThrow(AssertionError::new);
BigDecimal t1 = forecast1.getParameter(TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal vis1 = forecast1.getParameter(VISIBILITY).orElseThrow(AssertionError::new);
BigDecimal wd1 = forecast1.getParameter(WIND_DIRECTION).orElseThrow(AssertionError::new);
BigDecimal ws1 = forecast1.getParameter(WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal r1 = forecast1.getParameter(RELATIVE_HUMIDITY).orElseThrow(AssertionError::new);
BigDecimal tstm1 = forecast1.getParameter(THUNDER_PROBABILITY).orElseThrow(AssertionError::new);
BigDecimal tcc1 = forecast1.getParameter(TOTAL_CLOUD_COVER).orElseThrow(AssertionError::new);
BigDecimal lcc1 = forecast1.getParameter(LOW_CLOUD_COVER).orElseThrow(AssertionError::new);
BigDecimal mcc1 = forecast1.getParameter(MEDIUM_CLOUD_COVER).orElseThrow(AssertionError::new);
BigDecimal hcc1 = forecast1.getParameter(HIGH_CLOUD_COVER).orElseThrow(AssertionError::new);
BigDecimal gust1 = forecast1.getParameter(GUST).orElseThrow(AssertionError::new);
BigDecimal pmin1 = forecast1.getParameter(PRECIPITATION_MIN).orElseThrow(AssertionError::new);
BigDecimal pmax1 = forecast1.getParameter(PRECIPITATION_MAX).orElseThrow(AssertionError::new);
BigDecimal spp1 = forecast1.getParameter(PERCENT_FROZEN).orElseThrow(AssertionError::new);
BigDecimal pcat1 = forecast1.getParameter(PRECIPITATION_CATEGORY).orElseThrow(AssertionError::new);
BigDecimal pmean1 = forecast1.getParameter(PRECIPITATION_MEAN).orElseThrow(AssertionError::new);
BigDecimal pmedian1 = forecast1.getParameter(PRECIPITATION_MEDIAN).orElseThrow(AssertionError::new);
BigDecimal wsymb1 = forecast1.getParameter(WEATHER_SYMBOL).orElseThrow(AssertionError::new);
assertEquals(0, msl1.compareTo(BigDecimal.valueOf(1014.5)));
assertEquals(0, t1.compareTo(BigDecimal.valueOf(19.2)));
assertEquals(0, vis1.compareTo(BigDecimal.valueOf(30.3)));
assertEquals(0, wd1.compareTo(BigDecimal.valueOf(307)));
assertEquals(0, ws1.compareTo(BigDecimal.valueOf(2.5)));
assertEquals(0, r1.compareTo(BigDecimal.valueOf(48)));
assertEquals(0, tstm1.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, tcc1.compareTo(BigDecimal.valueOf(4)));
assertEquals(0, lcc1.compareTo(BigDecimal.valueOf(1)));
assertEquals(0, mcc1.compareTo(BigDecimal.valueOf(3)));
assertEquals(0, hcc1.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, gust1.compareTo(BigDecimal.valueOf(7.9)));
assertEquals(0, pmin1.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, pmax1.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, spp1.compareTo(BigDecimal.valueOf(-9)));
assertEquals(0, pcat1.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, pmean1.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, pmedian1.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, wsymb1.compareTo(BigDecimal.valueOf(3)));
Forecast forecast2 = timeSeries1.getForecast(TIME, 1).orElseThrow(AssertionError::new);
BigDecimal msl2 = forecast2.getParameter(PRESSURE).orElseThrow(AssertionError::new);
BigDecimal t2 = forecast2.getParameter(TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal vis2 = forecast2.getParameter(VISIBILITY).orElseThrow(AssertionError::new);
BigDecimal wd2 = forecast2.getParameter(WIND_DIRECTION).orElseThrow(AssertionError::new);
BigDecimal ws2 = forecast2.getParameter(WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal r2 = forecast2.getParameter(RELATIVE_HUMIDITY).orElseThrow(AssertionError::new);
BigDecimal tstm2 = forecast2.getParameter(THUNDER_PROBABILITY).orElseThrow(AssertionError::new);
BigDecimal tcc2 = forecast2.getParameter(TOTAL_CLOUD_COVER).orElseThrow(AssertionError::new);
BigDecimal lcc2 = forecast2.getParameter(LOW_CLOUD_COVER).orElseThrow(AssertionError::new);
BigDecimal mcc2 = forecast2.getParameter(MEDIUM_CLOUD_COVER).orElseThrow(AssertionError::new);
BigDecimal hcc2 = forecast2.getParameter(HIGH_CLOUD_COVER).orElseThrow(AssertionError::new);
BigDecimal gust2 = forecast2.getParameter(GUST).orElseThrow(AssertionError::new);
BigDecimal pmin2 = forecast2.getParameter(PRECIPITATION_MIN).orElseThrow(AssertionError::new);
BigDecimal pmax2 = forecast2.getParameter(PRECIPITATION_MAX).orElseThrow(AssertionError::new);
BigDecimal spp2 = forecast2.getParameter(PERCENT_FROZEN).orElseThrow(AssertionError::new);
BigDecimal pcat2 = forecast2.getParameter(PRECIPITATION_CATEGORY).orElseThrow(AssertionError::new);
BigDecimal pmean2 = forecast2.getParameter(PRECIPITATION_MEAN).orElseThrow(AssertionError::new);
BigDecimal pmedian2 = forecast2.getParameter(PRECIPITATION_MEDIAN).orElseThrow(AssertionError::new);
BigDecimal wsymb2 = forecast2.getParameter(WEATHER_SYMBOL).orElseThrow(AssertionError::new);
assertEquals(0, msl2.compareTo(BigDecimal.valueOf(1014.5)));
assertEquals(0, t2.compareTo(BigDecimal.valueOf(19.2)));
assertEquals(0, vis2.compareTo(BigDecimal.valueOf(30.3)));
assertEquals(0, wd2.compareTo(BigDecimal.valueOf(307)));
assertEquals(0, ws2.compareTo(BigDecimal.valueOf(2.5)));
assertEquals(0, r2.compareTo(BigDecimal.valueOf(48)));
assertEquals(0, tstm2.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, tcc2.compareTo(BigDecimal.valueOf(4)));
assertEquals(0, lcc2.compareTo(BigDecimal.valueOf(1)));
assertEquals(0, mcc2.compareTo(BigDecimal.valueOf(3)));
assertEquals(0, hcc2.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, gust2.compareTo(BigDecimal.valueOf(7.9)));
assertEquals(0, pmin2.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, pmax2.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, spp2.compareTo(BigDecimal.valueOf(-9)));
assertEquals(0, pcat2.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, pmean2.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, pmedian2.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, wsymb2.compareTo(BigDecimal.valueOf(3)));
} }
@Test @Test
public void aggregationsTest() { public void forecastAggregatorTest() {
assertNotNull(timeSeries); assertNotNull(timeSeries1);
BigDecimal maxTemp = ForecastAggregator.max(timeSeries, 5, TEMPERATURE).orElseThrow(AssertionError::new); assertNotNull(timeSeries2);
BigDecimal minTemp = ForecastAggregator.min(timeSeries, 5, TEMPERATURE).orElseThrow(AssertionError::new); BigDecimal maxTempT1F0 = ForecastAggregator.max(timeSeries1, 0, TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal maxWind = ForecastAggregator.max(timeSeries, 5, WIND_SPEED).orElseThrow(AssertionError::new); BigDecimal minTempT1F0 = ForecastAggregator.min(timeSeries1, 0, TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal minWind = ForecastAggregator.min(timeSeries, 5, WIND_SPEED).orElseThrow(AssertionError::new); BigDecimal maxWindT1F0 = ForecastAggregator.max(timeSeries1, 0, WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal totalPrecip = ForecastAggregator.total(timeSeries, 5, PRECIPITATION_MEAN) BigDecimal minWindT1F0 = ForecastAggregator.min(timeSeries1, 0, WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal totalPrecipT1F0 = ForecastAggregator.total(timeSeries1, 0, PRECIPITATION_MEAN)
.orElseThrow(AssertionError::new);
BigDecimal noonPressureT1F0 = ForecastAggregator.noonOrFirst(timeSeries1, 0, PRESSURE)
.orElseThrow(AssertionError::new); .orElseThrow(AssertionError::new);
assertEquals(0, maxTemp.compareTo(BigDecimal.valueOf(7.5))); assertEquals(0, maxTempT1F0.compareTo(BigDecimal.valueOf(20.8)));
assertEquals(0, minTemp.compareTo(BigDecimal.valueOf(4.2))); assertEquals(0, minTempT1F0.compareTo(BigDecimal.valueOf(12.2)));
assertEquals(0, maxWind.compareTo(BigDecimal.valueOf(4.4))); assertEquals(0, maxWindT1F0.compareTo(BigDecimal.valueOf(4)));
assertEquals(0, minWind.compareTo(BigDecimal.valueOf(3.7))); assertEquals(0, minWindT1F0.compareTo(BigDecimal.valueOf(0.6)));
assertEquals(0, totalPrecip.compareTo(BigDecimal.valueOf(2.4))); assertEquals(0, totalPrecipT1F0.compareTo(BigDecimal.valueOf(0)));
assertEquals(0, noonPressureT1F0.compareTo(BigDecimal.valueOf(1015.6)));
BigDecimal maxTempT1F9 = ForecastAggregator.max(timeSeries1, 9, TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal minTempT1F9 = ForecastAggregator.min(timeSeries1, 9, TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal maxWindT1F9 = ForecastAggregator.max(timeSeries1, 9, WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal minWindT1F9 = ForecastAggregator.min(timeSeries1, 9, WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal totalPrecipT1F9 = ForecastAggregator.total(timeSeries1, 9, PRECIPITATION_MEAN)
.orElseThrow(AssertionError::new);
BigDecimal noonPressureT1F9 = ForecastAggregator.noonOrFirst(timeSeries1, 9, PRESSURE)
.orElseThrow(AssertionError::new);
assertEquals(0, maxTempT1F9.compareTo(BigDecimal.valueOf(21.5)));
assertEquals(0, minTempT1F9.compareTo(BigDecimal.valueOf(14.6)));
assertEquals(0, maxWindT1F9.compareTo(BigDecimal.valueOf(1.6)));
assertEquals(0, minWindT1F9.compareTo(BigDecimal.valueOf(1.4)));
assertEquals(0, totalPrecipT1F9.compareTo(BigDecimal.valueOf(3.6)));
assertEquals(0, noonPressureT1F9.compareTo(BigDecimal.valueOf(1016.2)));
BigDecimal maxTempT2F0 = ForecastAggregator.max(timeSeries2, 0, TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal minTempT2F0 = ForecastAggregator.min(timeSeries2, 0, TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal maxWindT2F0 = ForecastAggregator.max(timeSeries2, 0, WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal minWindT2F0 = ForecastAggregator.min(timeSeries2, 0, WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal totalPrecipT2F0 = ForecastAggregator.total(timeSeries2, 0, PRECIPITATION_MEAN)
.orElseThrow(AssertionError::new);
BigDecimal noonPressureT2F0 = ForecastAggregator.noonOrFirst(timeSeries2, 0, PRESSURE)
.orElseThrow(AssertionError::new);
assertEquals(0, maxTempT2F0.compareTo(BigDecimal.valueOf(22.5)));
assertEquals(0, minTempT2F0.compareTo(BigDecimal.valueOf(9.7)));
assertEquals(0, maxWindT2F0.compareTo(BigDecimal.valueOf(5.7)));
assertEquals(0, minWindT2F0.compareTo(BigDecimal.valueOf(2)));
assertEquals(0, totalPrecipT2F0.compareTo(BigDecimal.valueOf(0.3)));
assertEquals(0, noonPressureT2F0.compareTo(BigDecimal.valueOf(1013.7)));
BigDecimal maxTempT2F9 = ForecastAggregator.max(timeSeries2, 9, TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal minTempT2F9 = ForecastAggregator.min(timeSeries2, 9, TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal maxWindT2F9 = ForecastAggregator.max(timeSeries2, 9, WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal minWindT2F9 = ForecastAggregator.min(timeSeries2, 9, WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal totalPrecipT2F9 = ForecastAggregator.total(timeSeries2, 9, PRECIPITATION_MEAN)
.orElseThrow(AssertionError::new);
BigDecimal noonPressureT2F9 = ForecastAggregator.noonOrFirst(timeSeries2, 9, PRESSURE)
.orElseThrow(AssertionError::new);
assertEquals(0, maxTempT2F9.compareTo(BigDecimal.valueOf(22.4)));
assertEquals(0, minTempT2F9.compareTo(BigDecimal.valueOf(15.2)));
assertEquals(0, maxWindT2F9.compareTo(BigDecimal.valueOf(1.3)));
assertEquals(0, minWindT2F9.compareTo(BigDecimal.valueOf(0.7)));
assertEquals(0, totalPrecipT2F9.compareTo(BigDecimal.valueOf(2.4)));
assertEquals(0, noonPressureT2F9.compareTo(BigDecimal.valueOf(1014.6)));
BigDecimal maxTempT3F0 = ForecastAggregator.max(timeSeries3, 0, TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal minTempT3F0 = ForecastAggregator.min(timeSeries3, 0, TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal maxWindT3F0 = ForecastAggregator.max(timeSeries3, 0, WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal minWindT3F0 = ForecastAggregator.min(timeSeries3, 0, WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal totalPrecipT3F0 = ForecastAggregator.total(timeSeries3, 0, PRECIPITATION_MEAN)
.orElseThrow(AssertionError::new);
BigDecimal noonPressureT3F0 = ForecastAggregator.noonOrFirst(timeSeries3, 0, PRESSURE)
.orElseThrow(AssertionError::new);
assertEquals(0, maxTempT3F0.compareTo(BigDecimal.valueOf(18.6)));
assertEquals(0, minTempT3F0.compareTo(BigDecimal.valueOf(14.1)));
assertEquals(0, maxWindT3F0.compareTo(BigDecimal.valueOf(6)));
assertEquals(0, minWindT3F0.compareTo(BigDecimal.valueOf(4.9)));
assertEquals(0, totalPrecipT3F0.compareTo(BigDecimal.valueOf(0.5)));
assertEquals(0, noonPressureT3F0.compareTo(BigDecimal.valueOf(1012.6)));
BigDecimal maxTempT3F9 = ForecastAggregator.max(timeSeries3, 9, TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal minTempT3F9 = ForecastAggregator.min(timeSeries3, 9, TEMPERATURE).orElseThrow(AssertionError::new);
BigDecimal maxWindT3F9 = ForecastAggregator.max(timeSeries3, 9, WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal minWindT3F9 = ForecastAggregator.min(timeSeries3, 9, WIND_SPEED).orElseThrow(AssertionError::new);
BigDecimal totalPrecipT3F9 = ForecastAggregator.total(timeSeries3, 9, PRECIPITATION_MEAN)
.orElseThrow(AssertionError::new);
BigDecimal noonPressureT3F9 = ForecastAggregator.noonOrFirst(timeSeries3, 9, PRESSURE)
.orElseThrow(AssertionError::new);
assertEquals(0, maxTempT3F9.compareTo(BigDecimal.valueOf(22.6)));
assertEquals(0, minTempT3F9.compareTo(BigDecimal.valueOf(15.5)));
assertEquals(0, maxWindT3F9.compareTo(BigDecimal.valueOf(1)));
assertEquals(0, minWindT3F9.compareTo(BigDecimal.valueOf(0.9)));
assertEquals(0, totalPrecipT3F9.compareTo(BigDecimal.valueOf(3.6)));
assertEquals(0, noonPressureT3F9.compareTo(BigDecimal.valueOf(1016.6)));
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long