diff --git a/CHANGELOG.md b/CHANGELOG.md index 98d542b94..88fd24b66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added converters documentation [#1139](https://github.com/ie3-institute/PowerSystemDataModel/issues/1139) - Added abstraction for power value sources [#1438](https://github.com/ie3-institute/PowerSystemDataModel/issues/1438) - Add ground temperatures level 1 and 2 as option to weather data. [#1343](https://github.com/ie3-institute/PowerSystemDataModel/issues/1343) +- Add validation for required fields in ICON and COSMO `TimebasedWeatherValueFactories` [#1537](https://github.com/ie3-institute/PowerSystemDataModel/issues/1537) ### Fixed - Fixed small issues in tests [#1400](https://github.com/ie3-institute/PowerSystemDataModel/issues/1400) diff --git a/docs/readthedocs/models/input/additionaldata/weathersource.md b/docs/readthedocs/models/input/additionaldata/weathersource.md index a59e79c55..ea8c8437b 100644 --- a/docs/readthedocs/models/input/additionaldata/weathersource.md +++ b/docs/readthedocs/models/input/additionaldata/weathersource.md @@ -71,5 +71,7 @@ Weather data is comprised of five key components: - Ground temperature at level 2 for this coordinate. - K (Kelvin) ``` +All components listed above are required, except groundTemperatureLevel1 and groundTemperatureLevel2, which are optional. + Weather data in COSMO and ICON formats is supported. Additional optional weather data can also be provided. The ground temperature measurements at level 1 and level 2 depth are used. Underground cables are typically laid at around 80 cm depth. \ No newline at end of file diff --git a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactory.java index 33d705b30..cc026afda 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactory.java @@ -5,6 +5,7 @@ */ package edu.ie3.datamodel.io.factory.timeseries; +import edu.ie3.datamodel.exceptions.FactoryException; import edu.ie3.datamodel.models.StandardUnits; import edu.ie3.datamodel.models.timeseries.individual.TimeBasedValue; import edu.ie3.datamodel.models.value.WeatherValue; @@ -67,6 +68,15 @@ protected List> getFields(Class entityClass) { @Override protected TimeBasedValue buildModel(TimeBasedWeatherValueData data) { + Set requiredFields = + newSet( + TIME, + DIFFUSE_IRRADIANCE, + DIRECT_IRRADIANCE, + TEMPERATURE, + WIND_DIRECTION, + WIND_VELOCITY); + validatedRequiredFields(data, requiredFields); Point coordinate = data.getCoordinate(); ZonedDateTime time = timeUtil.toZonedDateTime(data.getField(TIME)); ComparableQuantity directIrradiance = @@ -96,4 +106,21 @@ protected TimeBasedValue buildModel(TimeBasedWeatherValueData data return new TimeBasedValue<>(time, weatherValue); } + + /** + * * Validates that all required fields are present and not empty in the provided data + * + * @param data the data to validate + * @param requiredFields the fields that must be present and non-empty + * @throws FactoryException if any required field is missing or empty + */ + protected void validatedRequiredFields( + TimeBasedWeatherValueData data, Set requiredFields) { + for (String field : requiredFields) { + String value = data.getField(field); + if (value == null || value.isEmpty()) { + throw new FactoryException("The field \"" + field + "\" is missing or empty."); + } + } + } } diff --git a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactory.java index 8dbea5357..c9dbb4757 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactory.java @@ -5,6 +5,7 @@ */ package edu.ie3.datamodel.io.factory.timeseries; +import edu.ie3.datamodel.exceptions.FactoryException; import edu.ie3.datamodel.models.StandardUnits; import edu.ie3.datamodel.models.timeseries.individual.TimeBasedValue; import edu.ie3.datamodel.models.value.WeatherValue; @@ -81,6 +82,15 @@ protected List> getFields(Class entityClass) { @Override protected TimeBasedValue buildModel(TimeBasedWeatherValueData data) { + Set requiredFields = + newSet( + TIME, + DIFFUSE_IRRADIANCE, + DIRECT_IRRADIANCE, + TEMPERATURE, + WIND_VELOCITY_U, + WIND_VELOCITY_V); + validatedRequiredFields(data, requiredFields); Point coordinate = data.getCoordinate(); ZonedDateTime time = timeUtil.toZonedDateTime(data.getField(TIME)); ComparableQuantity directIrradiance = @@ -155,4 +165,21 @@ private static ComparableQuantity getWindVelocity(TimeBasedWeatherValueDa double velocity = Math.sqrt(Math.pow(u, 2) + Math.pow(v, 2)); return Quantities.getQuantity(velocity, Units.METRE_PER_SECOND).to(StandardUnits.WIND_VELOCITY); } + + /** + * * Validates that all required fields are present and not empty in the provided data + * + * @param data the data to validate + * @param requiredFields the fields that must be present and non-empty + * @throws FactoryException if any required field is missing or empty + */ + protected void validatedRequiredFields( + TimeBasedWeatherValueData data, Set requiredFields) { + for (String field : requiredFields) { + String value = data.getField(field); + if (value == null || value.isEmpty()) { + throw new FactoryException("The field \"" + field + "\" is missing or empty."); + } + } + } } diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy index c6d44564c..fb30156b5 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy @@ -16,14 +16,13 @@ import tech.units.indriya.quantity.Quantities class CosmoTimeBasedWeatherValueFactoryTest extends Specification { - def "A PsdmTimeBasedWeatherValueFactory should be able to create time series with missing values"() { + def "A PsdmTimeBasedWeatherValueFactory should throw an Exception if a required field is empty"() { given: def factory = new CosmoTimeBasedWeatherValueFactory() def coordinate = CosmoWeatherTestData.COORDINATE_193186 - def time = TimeUtil.withDefaults.toZonedDateTime("2019-01-01T00:00:00Z") Map parameter = [ - "time" : TimeUtil.withDefaults.toString(time), + "time" : "2019-01-01T00:00:00Z", "diffuseIrradiance" : "282.671997070312", "directIrradiance" : "286.872985839844", "temperature" : "", @@ -35,23 +34,15 @@ class CosmoTimeBasedWeatherValueFactoryTest extends Specification { def data = new TimeBasedWeatherValueData(parameter, coordinate) - def expectedResults = new TimeBasedValue( - time, new WeatherValue(coordinate, - Quantities.getQuantity(286.872985839844d, StandardUnits.SOLAR_IRRADIANCE), - Quantities.getQuantity(282.671997070312d, StandardUnits.SOLAR_IRRADIANCE), - null, - Quantities.getQuantity(0d, StandardUnits.WIND_DIRECTION), - Quantities.getQuantity(1.66103506088257d, StandardUnits.WIND_VELOCITY), - Optional.empty(), - Optional.empty())) - when: - def model = factory.buildModel(data) + factory.buildModel(data) then: - Objects.equals(model,expectedResults) + def exception = thrown(FactoryException) + exception.message == 'The field "temperature" is missing or empty.' } + def "A PsdmTimeBasedWeatherValueFactory should be able to create time series values"() { given: def factory = new CosmoTimeBasedWeatherValueFactory() diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy index 5749fe9fc..e80771825 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy @@ -195,4 +195,29 @@ class IconTimeBasedWeatherValueFactoryTest extends Specification { then: !model.equals(expectedResults) } -} \ No newline at end of file + + def "A IconTimeBasedWeatherValueFactory should throw an Exception if the required field 't2m' is empty"() { + given: + def factory = new IconTimeBasedWeatherValueFactory() + def coordinate = CosmoWeatherTestData.COORDINATE_67775 + + Map parameter = [ + "time" : "2019-01-01T00:00:00Z", + "aswdifdS" : "1.0", + "aswdirS" : "2.0", + "t2m" : "", + "u131m" : "4.0", + "v131m" : "5.0", + "coordinateId": "67775" + ] + + def data = new TimeBasedWeatherValueData(parameter, coordinate) + + when: + factory.buildModel(data) + + then: + def exception = thrown(FactoryException) + exception.message == 'The field "t2m" is missing or empty.' + } +}