diff --git a/CHANGELOG.md b/CHANGELOG.md index 37d4cc181..8beeea58d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] +### Added +- Enhancing value retrieval in `TimeSeriesSource` [1280](https://github.com/ie3-institute/PowerSystemDataModel/issues/1280) + ### Fixed - Fixed handling of `CongestionResult.InputModelType` in `EntityProcessor` [#1325](https://github.com/ie3-institute/PowerSystemDataModel/issues/1325) diff --git a/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesSource.java b/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesSource.java index 2a80e0a78..338fb510d 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesSource.java @@ -52,6 +52,22 @@ public abstract IndividualTimeSeries getTimeSeries(ClosedInterval getValue(ZonedDateTime time); + /** + * Method to retrieve the value of the given time or the last timestamp before the given time. + * + * @param time given time + * @return an option for a value + */ + public Optional getValueOrLast(ZonedDateTime time) { + Optional value = getValue(time); + + if (value.isEmpty()) { + return getPreviousTimeBasedValue(time).map(TimeBasedValue::getValue); + } + + return value; + } + public abstract Optional> getPreviousTimeBasedValue(ZonedDateTime time); /** @@ -61,4 +77,12 @@ public abstract IndividualTimeSeries getTimeSeries(ClosedInterval getTimeKeysAfter(ZonedDateTime time); + + /** + * Method to return all last known time keys before a given timestamp. + * + * @param time given time + * @return an option for the time key + */ + public abstract Optional getLastTimeKeyBefore(ZonedDateTime time); } diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java index 4b5acd0ba..d10982e9b 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java @@ -142,6 +142,11 @@ public List getTimeKeysAfter(ZonedDateTime time) { return timeSeries.getTimeKeysAfter(time); } + @Override + public Optional getLastTimeKeyBefore(ZonedDateTime time) { + return timeSeries.getPreviousDateTime(time); + } + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- /** diff --git a/src/main/java/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesSource.java b/src/main/java/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesSource.java index 7016feb5d..98a09554f 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesSource.java @@ -199,6 +199,15 @@ public List getTimeKeysAfter(ZonedDateTime time) { .toList(); } + @Override + public Optional getLastTimeKeyBefore(ZonedDateTime time) { + return dataSource + .executeQuery( + queryForValueBefore, ps -> ps.setTimestamp(1, Timestamp.from(time.toInstant()))) + .map(valueFactory::extractTime) + .findFirst(); + } + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- /** Creates a set of TimeBasedValues from database */ diff --git a/src/main/java/edu/ie3/datamodel/models/timeseries/TimeSeries.java b/src/main/java/edu/ie3/datamodel/models/timeseries/TimeSeries.java index b42238286..f9fa97891 100644 --- a/src/main/java/edu/ie3/datamodel/models/timeseries/TimeSeries.java +++ b/src/main/java/edu/ie3/datamodel/models/timeseries/TimeSeries.java @@ -62,7 +62,7 @@ public Optional> getTimeBasedValue(ZonedDateTime time) { * @param time Reference in time * @return The next earlier known time instant */ - protected abstract Optional getPreviousDateTime(ZonedDateTime time); + public abstract Optional getPreviousDateTime(ZonedDateTime time); /** * Get the next later known time instant diff --git a/src/main/java/edu/ie3/datamodel/models/timeseries/individual/IndividualTimeSeries.java b/src/main/java/edu/ie3/datamodel/models/timeseries/individual/IndividualTimeSeries.java index 1ae846da3..9be8f4531 100644 --- a/src/main/java/edu/ie3/datamodel/models/timeseries/individual/IndividualTimeSeries.java +++ b/src/main/java/edu/ie3/datamodel/models/timeseries/individual/IndividualTimeSeries.java @@ -53,7 +53,7 @@ public Optional getValue(ZonedDateTime time) { } @Override - protected Optional getPreviousDateTime(ZonedDateTime time) { + public Optional getPreviousDateTime(ZonedDateTime time) { return timeToValue.keySet().stream() .filter(valueTime -> valueTime.compareTo(time) < 0) .max(Comparator.naturalOrder()); diff --git a/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/LoadProfileTimeSeries.java b/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/LoadProfileTimeSeries.java index c024e4efc..7c896fcbe 100644 --- a/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/LoadProfileTimeSeries.java +++ b/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/LoadProfileTimeSeries.java @@ -78,7 +78,7 @@ public Set> getEntries() { } @Override - protected Optional getPreviousDateTime(ZonedDateTime time) { + public Optional getPreviousDateTime(ZonedDateTime time) { return Optional.of(time.minusMinutes(15)); } diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSourceTest.groovy index 47d662b9a..d6b6e48fb 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSourceTest.groovy @@ -47,6 +47,19 @@ class CsvTimeSeriesSourceTest extends Specification implements CsvTestDataMeta { actual.data.get() == expected } + def "The csv time series source returns the last value, if there is no current value"() { + given: + def factory = new TimeBasedSimpleValueFactory(EnergyPriceValue) + def source = new CsvTimeSeriesSource(";", timeSeriesFolderPath, new FileNamingStrategy(), UUID.fromString("2fcb3e53-b94a-4b96-bea4-c469e499f1a1"), Path.of("its_c_2fcb3e53-b94a-4b96-bea4-c469e499f1a1"), EnergyPriceValue, factory) + def time = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:13:00Z") + + when: + def actual = source.getValueOrLast(time) + + then: + actual == Optional.of(new EnergyPriceValue(Quantities.getQuantity(100.0, ENERGY_PRICE))) + } + def "The csv time series source returns the time keys after a given key correctly"() { given: def factory = new TimeBasedSimpleValueFactory(EnergyPriceValue) @@ -63,6 +76,27 @@ class CsvTimeSeriesSourceTest extends Specification implements CsvTestDataMeta { ] } + def "The csv time series source returns the time key before a given key correctly"() { + given: + def factory = new TimeBasedSimpleValueFactory(EnergyPriceValue) + def source = new CsvTimeSeriesSource(";", timeSeriesFolderPath, new FileNamingStrategy(), UUID.fromString("2fcb3e53-b94a-4b96-bea4-c469e499f1a1"), Path.of("its_c_2fcb3e53-b94a-4b96-bea4-c469e499f1a1"), EnergyPriceValue, factory) + + when: + def time = TimeUtil.withDefaults.toZonedDateTime(timeKey) + + def actual = source.getLastTimeKeyBefore(time) + + then: + actual == expectedKey + + where: + timeKey | expectedKey + "2019-12-31T23:59:59Z" | Optional.empty() + "2020-01-01T00:00:00Z" | Optional.empty() + "2020-01-01T00:15:00Z" | Optional.of(TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:00:00Z")) + "2020-01-03T00:00:00Z" | Optional.of(TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:15:00Z")) + } + def "The factory method in csv time series source refuses to build time series with unsupported column type"() { given: def metaInformation = new CsvIndividualTimeSeriesMetaInformation(UUID.fromString("8bc9120d-fb9b-4484-b4e3-0cdadf0feea9"), ColumnScheme.WEATHER, Path.of("its_weather_8bc9120d-fb9b-4484-b4e3-0cdadf0feea9")) diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesSourceIT.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesSourceIT.groovy index 0b960aba9..08d067e2a 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesSourceIT.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesSourceIT.groovy @@ -6,6 +6,7 @@ package edu.ie3.datamodel.io.source.sql import static edu.ie3.test.common.TimeSeriesSourceTestData.* +import static edu.ie3.util.quantities.PowerSystemUnits.KILOWATT import edu.ie3.datamodel.exceptions.SourceException import edu.ie3.datamodel.io.connectors.SqlConnector @@ -23,6 +24,7 @@ import org.testcontainers.spock.Testcontainers import org.testcontainers.utility.MountableFile import spock.lang.Shared import spock.lang.Specification +import tech.units.indriya.quantity.Quantities import java.time.format.DateTimeFormatter @@ -124,6 +126,17 @@ class SqlTimeSeriesSourceIT extends Specification implements TestContainerHelper value.get() == P_VALUE_00MIN } + def "The cSqlTimeSeriesSource returns the last value, if there is no current value"() { + given: + def time = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:13:00Z") + + when: + def actual = pSource.getValueOrLast(time) + + then: + actual == Optional.of(new PValue(Quantities.getQuantity(1000.0, KILOWATT))) + } + def "A SqlTimeSeriesSource is able to return the previous value for a given time"() { when: def actual = pSource.getPreviousTimeBasedValue(time) @@ -173,4 +186,21 @@ class SqlTimeSeriesSourceIT extends Specification implements TestContainerHelper TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:15:00Z") ] } + + def "The SqlTimeSeriesSource returns the time key before a given key correctly"() { + when: + def time = TimeUtil.withDefaults.toZonedDateTime(timeKey) + + def actual = pSource.getLastTimeKeyBefore(time) + + then: + actual == expectedKey + + where: + timeKey | expectedKey + "2019-12-31T23:59:59Z" | Optional.empty() + "2020-01-01T00:00:00Z" | Optional.empty() + "2020-01-01T00:15:00Z" | Optional.of(TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:00:00Z")) + "2020-01-03T00:00:00Z" | Optional.of(TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:15:00Z")) + } }