Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions docs/readthedocs/models/input/additionaldata/weathersource.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -67,6 +68,15 @@ protected List<Set<String>> getFields(Class<?> entityClass) {

@Override
protected TimeBasedValue<WeatherValue> buildModel(TimeBasedWeatherValueData data) {
Set<String> 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<Irradiance> directIrradiance =
Expand Down Expand Up @@ -96,4 +106,21 @@ protected TimeBasedValue<WeatherValue> 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<String> 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.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -81,6 +82,15 @@ protected List<Set<String>> getFields(Class<?> entityClass) {

@Override
protected TimeBasedValue<WeatherValue> buildModel(TimeBasedWeatherValueData data) {
Set<String> 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<Irradiance> directIrradiance =
Expand Down Expand Up @@ -155,4 +165,21 @@ private static ComparableQuantity<Speed> 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<String> 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.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> parameter = [
"time" : TimeUtil.withDefaults.toString(time),
"time" : "2019-01-01T00:00:00Z",
"diffuseIrradiance" : "282.671997070312",
"directIrradiance" : "286.872985839844",
"temperature" : "",
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,29 @@ class IconTimeBasedWeatherValueFactoryTest extends Specification {
then:
!model.equals(expectedResults)
}
}

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<String, String> 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.'
}
}