From cf42dcd3fd157c1548add30387ffb7a99332f2bd Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Sun, 19 Oct 2025 19:44:51 +0200 Subject: [PATCH 1/6] Change Evcs.locationType from single element to list --- CHANGELOG.md | 1 + .../input/participant/EvcsInputFactory.java | 28 ++++++--- .../ie3/datamodel/io/processor/Processor.java | 10 ++- .../ie3/datamodel/io/sink/CsvFileSink.java | 4 +- .../models/input/system/EvcsInput.java | 57 +++++++++-------- .../evcslocation/EvcsLocationTypeUtils.java | 61 ++++++++++++++++++- .../participant/EvcsInputFactoryTest.groovy | 13 ++-- .../input/InputEntityProcessorTest.groovy | 3 +- .../models/input/system/EvcsInputTest.groovy | 10 ++- .../EvcsLocationTypeUtilsTest.groovy | 20 +++++- .../common/SystemParticipantTestData.groovy | 2 +- .../datamodel/io/sink/_sql/input_entities.sql | 2 +- .../io/source/csv/_joint_grid/evcs_input.csv | 2 +- .../source/csv/_participants/evcs_input.csv | 2 +- 14 files changed, 158 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d96d3ef44e..36000353d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extend `GermanVoltageLevelUtils` with more synonymousIds [#143](https://github.com/ie3-institute/PowerSystemDataModel/issues/143) - Change spotless to use googleJavaFormat('1.28.0') [#1409](https://github.com/ie3-institute/PowerSystemDataModel/issues/1409) - Change `TimeSeries` to no longer extend `UniqueEntity` [#1441](https://github.com/ie3-institute/PowerSystemDataModel/issues/1441) +- Change Evcs.locationType from single element to list [#1460](https://github.com/ie3-institute/PowerSystemDataModel/issues/1460) ## [8.1.0] - 2025-07-25 diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java index 46b75a9f95..e423789665 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java @@ -18,6 +18,7 @@ import edu.ie3.datamodel.models.input.system.type.chargingpoint.ChargingPointTypeUtils; import edu.ie3.datamodel.models.input.system.type.evcslocation.EvcsLocationType; import edu.ie3.datamodel.models.input.system.type.evcslocation.EvcsLocationTypeUtils; +import java.util.List; import java.util.UUID; /** @@ -33,7 +34,7 @@ public class EvcsInputFactory private static final String TYPE = "type"; private static final String CHARGING_POINTS = "chargingPoints"; private static final String COS_PHI_RATED = "cosPhiRated"; - private static final String LOCATION_TYPE = "locationType"; + private static final String LOCATION_TYPES = "locationTypes"; private static final String V2G_SUPPORT = "v2gSupport"; public EvcsInputFactory() { @@ -42,7 +43,7 @@ public EvcsInputFactory() { @Override protected String[] getAdditionalFields() { - return new String[] {TYPE, CHARGING_POINTS, COS_PHI_RATED, LOCATION_TYPE, V2G_SUPPORT}; + return new String[] {TYPE, CHARGING_POINTS, COS_PHI_RATED, LOCATION_TYPES, V2G_SUPPORT}; } @Override @@ -68,15 +69,24 @@ protected EvcsInput buildModel( final int chargingPoints = data.getInt(CHARGING_POINTS); final double cosPhi = data.getDouble(COS_PHI_RATED); - final EvcsLocationType locationType; + final List locationTypes; try { - locationType = EvcsLocationTypeUtils.parse(data.getField(LOCATION_TYPE)); - } catch (ParsingException e) { + String locationTypesField = data.getField(LOCATION_TYPES); + if (locationTypesField.contains(",")) { + locationTypes = EvcsLocationTypeUtils.parse(locationTypesField); + } else { + locationTypes = List.of(EvcsLocationTypeUtils.parseSingle(locationTypesField)); + } + } catch (ParsingException | RuntimeException e) { + Throwable cause = + e instanceof RuntimeException && e.getCause() instanceof ParsingException + ? e.getCause() + : e; throw new FactoryException( String.format( - "Exception while trying to parse field \"%s\" with supposed int value \"%s\"", - LOCATION_TYPE, data.getField(LOCATION_TYPE)), - e); + "Exception while trying to parse field \"%s\" with supposed value \"%s\"", + LOCATION_TYPES, data.getField(LOCATION_TYPES)), + cause); } final boolean v2gSupport = data.getBoolean(V2G_SUPPORT); @@ -92,7 +102,7 @@ protected EvcsInput buildModel( type, chargingPoints, cosPhi, - locationType, + locationTypes, v2gSupport); } } diff --git a/src/main/java/edu/ie3/datamodel/io/processor/Processor.java b/src/main/java/edu/ie3/datamodel/io/processor/Processor.java index d232dbf27b..0042f8a141 100644 --- a/src/main/java/edu/ie3/datamodel/io/processor/Processor.java +++ b/src/main/java/edu/ie3/datamodel/io/processor/Processor.java @@ -230,8 +230,16 @@ protected String processMethodResult( "DayOfWeek", "Season", "ChargingPointType", - "EvcsLocationType" -> + "EvcsLocationTypes" -> resultStringBuilder.append(methodReturnObject.toString()); + case "List", "ArrayList" -> { + if (methodReturnObject instanceof Collection collection) { + resultStringBuilder.append( + collection.stream().map(Object::toString).collect(Collectors.joining(",", "[", "]"))); + } else { + resultStringBuilder.append(methodReturnObject.toString()); + } + } case "Quantity", "ComparableQuantity" -> resultStringBuilder.append(handleQuantity((Quantity) methodReturnObject, fieldName)); case "Optional" -> diff --git a/src/main/java/edu/ie3/datamodel/io/sink/CsvFileSink.java b/src/main/java/edu/ie3/datamodel/io/sink/CsvFileSink.java index 4b08d00f62..ba3ebb068f 100644 --- a/src/main/java/edu/ie3/datamodel/io/sink/CsvFileSink.java +++ b/src/main/java/edu/ie3/datamodel/io/sink/CsvFileSink.java @@ -330,8 +330,8 @@ private String[] csvHeaderElements(String[] strings) { } /** - * Transforms a provided map of string to string to valid csv formatted strings (according to csv - * specification RFC 4180) + * Transforms a provided map of string to string into valid csv formatted strings (according to + * csv specification RFC 4180) * * @param entityFieldData a string to string map that should be processed * @return a new map with valid csv formatted keys and values strings diff --git a/src/main/java/edu/ie3/datamodel/models/input/system/EvcsInput.java b/src/main/java/edu/ie3/datamodel/models/input/system/EvcsInput.java index 50f2a38aec..3f9be027bb 100644 --- a/src/main/java/edu/ie3/datamodel/models/input/system/EvcsInput.java +++ b/src/main/java/edu/ie3/datamodel/models/input/system/EvcsInput.java @@ -12,6 +12,7 @@ import edu.ie3.datamodel.models.input.system.characteristic.ReactivePowerCharacteristic; import edu.ie3.datamodel.models.input.system.type.chargingpoint.ChargingPointType; import edu.ie3.datamodel.models.input.system.type.evcslocation.EvcsLocationType; +import java.util.List; import java.util.Objects; import java.util.UUID; import javax.measure.quantity.Power; @@ -28,8 +29,8 @@ public class EvcsInput extends SystemParticipantInput { /** Rated power factor */ private final double cosPhiRated; - /** Evcs location type */ - private final EvcsLocationType locationType; + /** Evcs location types (minimum one required) */ + private final List locationTypes; /** Whether charging station supports vehicle to grid */ private final boolean v2gSupport; @@ -45,7 +46,7 @@ public class EvcsInput extends SystemParticipantInput { * @param type type of the charging points available to this charging station * @param chargingPoints number of charging points available at this charging station * @param cosPhiRated rated cos phi - * @param locationType the location type + * @param locationTypes the location types (minimum one required) * @param v2gSupport whether charging station supports vehicle to grid */ public EvcsInput( @@ -59,13 +60,16 @@ public EvcsInput( ChargingPointType type, int chargingPoints, double cosPhiRated, - EvcsLocationType locationType, + List locationTypes, boolean v2gSupport) { super(uuid, id, operator, operationTime, node, qCharacteristics, em); + if (locationTypes == null || locationTypes.isEmpty()) { + throw new IllegalArgumentException("At least one location type must be provided"); + } this.type = type; this.chargingPoints = chargingPoints; this.cosPhiRated = cosPhiRated; - this.locationType = locationType; + this.locationTypes = List.copyOf(locationTypes); this.v2gSupport = v2gSupport; } @@ -79,7 +83,7 @@ public EvcsInput( * @param em The {@link EmInput} controlling this system participant. Null, if not applicable. * @param type type of the charging points available to this charging station * @param cosPhiRated rated cos phi - * @param locationType the location type + * @param locationTypes the location types (minimum one required) * @param v2gSupport whether charging station supports vehicle to grid */ public EvcsInput( @@ -92,7 +96,7 @@ public EvcsInput( EmInput em, ChargingPointType type, double cosPhiRated, - EvcsLocationType locationType, + List locationTypes, boolean v2gSupport) { this( uuid, @@ -105,7 +109,7 @@ public EvcsInput( type, 1, cosPhiRated, - locationType, + locationTypes, v2gSupport); } @@ -118,7 +122,7 @@ public EvcsInput( * @param type type of the charging points available to this charging station * @param chargingPoints number of charging points available at this charging station * @param cosPhiRated rated cos phi - * @param locationType the location type + * @param locationTypes the location types (minimum one required) * @param v2gSupport whether charging station supports vehicle to grid */ public EvcsInput( @@ -130,13 +134,16 @@ public EvcsInput( ChargingPointType type, int chargingPoints, double cosPhiRated, - EvcsLocationType locationType, + List locationTypes, boolean v2gSupport) { super(uuid, id, node, qCharacteristics, em); + if (locationTypes == null || locationTypes.isEmpty()) { + throw new IllegalArgumentException("At least one location type must be provided"); + } this.type = type; this.chargingPoints = chargingPoints; this.cosPhiRated = cosPhiRated; - this.locationType = locationType; + this.locationTypes = List.copyOf(locationTypes); this.v2gSupport = v2gSupport; } @@ -148,7 +155,7 @@ public EvcsInput( * @param em The {@link EmInput} controlling this system participant. Null, if not applicable. * @param type type of the charging points available to this charging station * @param cosPhiRated rated cos phi - * @param locationType the location type + * @param locationTypes the location types (minimum one required) * @param v2gSupport whether charging station supports vehicle to grid */ public EvcsInput( @@ -159,9 +166,9 @@ public EvcsInput( EmInput em, ChargingPointType type, double cosPhiRated, - EvcsLocationType locationType, + List locationTypes, boolean v2gSupport) { - this(uuid, id, node, qCharacteristics, em, type, 1, cosPhiRated, locationType, v2gSupport); + this(uuid, id, node, qCharacteristics, em, type, 1, cosPhiRated, locationTypes, v2gSupport); } public ChargingPointType getType() { @@ -176,8 +183,8 @@ public double getCosPhiRated() { return cosPhiRated; } - public EvcsLocationType getLocationType() { - return locationType; + public List getLocationTypes() { + return locationTypes; } public boolean getV2gSupport() { @@ -202,13 +209,13 @@ public boolean equals(Object o) { return chargingPoints == evcsInput.chargingPoints && Double.compare(evcsInput.cosPhiRated, cosPhiRated) == 0 && type.equals(evcsInput.type) - && locationType.equals(evcsInput.locationType) + && locationTypes.equals(evcsInput.locationTypes) && v2gSupport == evcsInput.v2gSupport; } @Override public int hashCode() { - return Objects.hash(super.hashCode(), type, chargingPoints, cosPhiRated, locationType); + return Objects.hash(super.hashCode(), type, chargingPoints, cosPhiRated, locationTypes); } @Override @@ -234,8 +241,8 @@ public String toString() { + chargingPoints + ", cosPhiRated=" + cosPhiRated - + ", locationType=" - + locationType + + ", locationTypes=" + + locationTypes + ", v2gSupport=" + getV2gSupport() + '}'; @@ -254,7 +261,7 @@ public static class EvcsInputCopyBuilder private ChargingPointType type; private int chargingPoints; private double cosPhiRated; - private EvcsLocationType locationType; + private List locationTypes; private boolean v2gSupport; public EvcsInputCopyBuilder(EvcsInput entity) { @@ -262,7 +269,7 @@ public EvcsInputCopyBuilder(EvcsInput entity) { this.type = entity.type; this.chargingPoints = entity.chargingPoints; this.cosPhiRated = entity.cosPhiRated; - this.locationType = entity.locationType; + this.locationTypes = entity.locationTypes; this.v2gSupport = entity.v2gSupport; } @@ -281,8 +288,8 @@ public EvcsInputCopyBuilder cosPhiRated(double cosPhiRated) { return thisInstance(); } - public EvcsInputCopyBuilder locationType(EvcsLocationType locationType) { - this.locationType = locationType; + public EvcsInputCopyBuilder locationTypes(List locationTypes) { + this.locationTypes = locationTypes; return thisInstance(); } @@ -310,7 +317,7 @@ public EvcsInput build() { type, chargingPoints, cosPhiRated, - locationType, + locationTypes, v2gSupport); } diff --git a/src/main/java/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtils.java b/src/main/java/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtils.java index ffb31c0765..7cb4dc6019 100644 --- a/src/main/java/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtils.java +++ b/src/main/java/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtils.java @@ -6,7 +6,10 @@ package edu.ie3.datamodel.models.input.system.type.evcslocation; import edu.ie3.datamodel.exceptions.ParsingException; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; /** * Utility class providing tools to retrieve {@link EvcsLocationType}s from string representation @@ -28,14 +31,66 @@ private EvcsLocationTypeUtils() { } /** - * Parsing a location type string into one {@link EvcsLocationType}. Matching the string is - * case-insensitive and all - and _ are removed. Throws exception, if type does not exist. + * Parsing a location type string into one {@link EvcsLocationType} or a list of + * EvcsLocationTypes. Matching the string is case-insensitive and all - and _ are removed. + * + * @param parsableString string to parse + * @return List + * @throws ParsingException if string does not represent a location type + */ + public static List parse(String parsableString) throws ParsingException { + if (parsableString == null || parsableString.trim().isEmpty()) { + throw new ParsingException("Location types string cannot be null or empty"); + } + + if (parsableString.startsWith("[") && parsableString.endsWith("]")) { + String listContent = parsableString.substring(1, parsableString.length() - 1); + if (listContent.isEmpty()) { + return java.util.Collections.emptyList(); + } + return Arrays.stream(listContent.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .map( + s -> { + try { + return parseSingle(s); + } catch (ParsingException e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toList()); + } + + // Check if it contains comma for multiple values (without brackets) + if (parsableString.contains(",")) { + return Arrays.stream(parsableString.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .map( + s -> { + try { + return parseSingle(s); + } catch (ParsingException e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toList()); + } else { + // Single value - wrap in List + return List.of(parseSingle(parsableString.trim())); + } + } + + /** + * Parsing a single location type string into one {@link EvcsLocationType}. Matching the string is + * case-insensitive and all - and _ are removed. * * @param parsableString string to parse * @return corresponding EvcsLocationType * @throws ParsingException if string does not represent a location type */ - public static EvcsLocationType parse(String parsableString) throws ParsingException { + public static EvcsLocationType parseSingle(String parsableString) throws ParsingException { final String key = toKey(parsableString); if (nameToType.containsKey(key)) return nameToType.get(key); else throw new ParsingException("EvcsLocationType '" + key + "' does not exist."); diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy index 9d3ccd7ed9..b68e0ea0dd 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy @@ -49,7 +49,7 @@ class EvcsInputFactoryTest extends Specification implements FactoryTestHelper { "type" : "Household", "chargingpoints" : "4", "cosphirated" : "0.95", - "locationtype" : "CHARGING_HUB_TOWN", + "locationtypes" : "CHARGING_HUB_TOWN, STREET", "v2gsupport" : "false" ] def inputClass = EvcsInput @@ -83,7 +83,10 @@ class EvcsInputFactoryTest extends Specification implements FactoryTestHelper { assert type == ChargingPointTypeUtils.HouseholdSocket assert chargingPoints == Integer.parseInt(parameter["chargingpoints"]) assert cosPhiRated == Double.parseDouble(parameter["cosphirated"]) - assert locationType == EvcsLocationType.CHARGING_HUB_TOWN + assert locationTypes == [ + EvcsLocationType.CHARGING_HUB_TOWN, + EvcsLocationType.STREET + ] assert !v2gSupport } } @@ -100,7 +103,7 @@ class EvcsInputFactoryTest extends Specification implements FactoryTestHelper { "type" : "-- invalid --", "chargingpoints" : "4", "cosphirated" : "0.95", - "locationtype" : "CHARGING_HUB_TOWN", + "locationtypes" : "[CHARGING_HUB_TOWN]", "v2gsupport" : "false" ] def inputClass = EvcsInput @@ -129,7 +132,7 @@ class EvcsInputFactoryTest extends Specification implements FactoryTestHelper { "type" : "Household", "chargingpoints" : "4", "cosphirated" : "0.95", - "locationType" : "-- invalid --", + "locationTypes" : "-- invalid --", "v2gsupport" : "false" ] def inputClass = EvcsInput @@ -143,6 +146,6 @@ class EvcsInputFactoryTest extends Specification implements FactoryTestHelper { then: input.failure - input.exception.get().cause.message == "Exception while trying to parse field \"locationType\" with supposed int value \"-- invalid --\"" + input.exception.get().cause.message == "Exception while trying to parse field \"locationTypes\" with supposed value \"-- invalid --\"" } } diff --git a/src/test/groovy/edu/ie3/datamodel/io/processor/input/InputEntityProcessorTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/processor/input/InputEntityProcessorTest.groovy index e22ff1a992..d389d46fa4 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/processor/input/InputEntityProcessorTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/processor/input/InputEntityProcessorTest.groovy @@ -234,7 +234,6 @@ class InputEntityProcessorTest extends Specification { "type" : SystemParticipantTestData.evInput.type.getUuid().toString(), "controllingEm" : SystemParticipantTestData.evInput.controllingEm.map((UniqueEntity::getUuid).andThen(UUID::toString)).orElse("") ] - LoadInput | SystemParticipantTestData.loadInput || [ "uuid" : SystemParticipantTestData.loadInput.uuid.toString(), "cosPhiRated" : SystemParticipantTestData.loadInput.cosPhiRated.toString(), @@ -283,7 +282,7 @@ class InputEntityProcessorTest extends Specification { "type" : SystemParticipantTestData.evcsInput.type.toString(), "cosPhiRated" : SystemParticipantTestData.evcsInput.cosPhiRated.toString(), "chargingPoints" : SystemParticipantTestData.evcsInput.chargingPoints.toString(), - "locationType" : SystemParticipantTestData.evcsInput.locationType.name(), + "locationTypes" : SystemParticipantTestData.evcsInput.locationTypes.toString(), "v2gSupport" : SystemParticipantTestData.evcsInput.v2gSupport.toString(), "controllingEm" : SystemParticipantTestData.evcsInput.controllingEm.map((UniqueEntity::getUuid).andThen(UUID::toString)).orElse("") ] diff --git a/src/test/groovy/edu/ie3/datamodel/models/input/system/EvcsInputTest.groovy b/src/test/groovy/edu/ie3/datamodel/models/input/system/EvcsInputTest.groovy index 064d98f298..80be9e416f 100644 --- a/src/test/groovy/edu/ie3/datamodel/models/input/system/EvcsInputTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/models/input/system/EvcsInputTest.groovy @@ -20,7 +20,9 @@ class EvcsInputTest extends Specification { def alteredEntity = evcsInput.copy() .type(ChargingPointTypeUtils.TeslaSuperChargerV3) .cosPhiRated(0.7d).chargingPoints(1) - .locationType(EvcsLocationType.CHARGING_HUB_HIGHWAY) + .locationTypes([ + EvcsLocationType.CHARGING_HUB_HIGHWAY + ]) .v2gSupport(true) .build() @@ -34,7 +36,9 @@ class EvcsInputTest extends Specification { assert type == ChargingPointTypeUtils.TeslaSuperChargerV3 assert cosPhiRated == 0.7d assert chargingPoints == 1 - assert locationType == EvcsLocationType.CHARGING_HUB_HIGHWAY + assert locationTypes == [ + EvcsLocationType.CHARGING_HUB_HIGHWAY + ] assert v2gSupport assert controllingEm == Optional.of(SystemParticipantTestData.emInput) } @@ -58,7 +62,7 @@ class EvcsInputTest extends Specification { assert sRated() == evcsInput.type.sRated * 2d assert cosPhiRated == evcsInput.cosPhiRated assert chargingPoints == evcsInput.chargingPoints - assert locationType == evcsInput.locationType + assert locationTypes == evcsInput.locationTypes assert v2gSupport == evcsInput.v2gSupport assert controllingEm == Optional.of(SystemParticipantTestData.emInput) } diff --git a/src/test/groovy/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtilsTest.groovy index b69bb3279e..280a4c76f6 100644 --- a/src/test/groovy/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtilsTest.groovy @@ -24,11 +24,12 @@ class EvcsLocationTypeUtilsTest extends Specification { def "The EvcsLocationTypeUtils should parse valid evcs location type strings as expected"() { given: - EvcsLocationType parsed = parse(parsableString) + List parsed = parse(parsableString) expect: - parsed == expectedObj - parsed.name().toLowerCase().replaceAll("[-_]*", "") == parsableString.toLowerCase().replaceAll("[-_]*", "") + parsed.size() == 1 + parsed[0] == expectedObj + parsed[0].name().toLowerCase().replaceAll("[-_]*", "") == parsableString.toLowerCase().replaceAll("[-_]*", "") where: parsableString || expectedObj @@ -42,6 +43,19 @@ class EvcsLocationTypeUtilsTest extends Specification { "charginghubhighway" || CHARGING_HUB_HIGHWAY // lower case without underscores } + def "The EvcsLocationTypeUtils should parse multiple evcs location type strings as expected"() { + given: + List parsed = parse(parsableString) + + expect: + parsed == expectedObj + + where: + parsableString || expectedObj + "WORK, CUSTOMER_PARKING" || [WORK, CUSTOMER_PARKING] + "HOME,WORK,STREET" || [HOME, WORK, STREET] + } + def "The EvcsLocationTypeUtils should throw exceptions as expected when invalid evcs location type string is provided"() { when: parse("--invalid--") diff --git a/src/test/groovy/edu/ie3/test/common/SystemParticipantTestData.groovy b/src/test/groovy/edu/ie3/test/common/SystemParticipantTestData.groovy index 7611b78bcb..7e66906356 100644 --- a/src/test/groovy/edu/ie3/test/common/SystemParticipantTestData.groovy +++ b/src/test/groovy/edu/ie3/test/common/SystemParticipantTestData.groovy @@ -336,7 +336,7 @@ class SystemParticipantTestData { ChargingPointTypeUtils.HouseholdSocket, 4, cosPhiRated, - EvcsLocationType.HOME, + [EvcsLocationType.HOME], v2gSupport ) diff --git a/src/test/resources/edu/ie3/datamodel/io/sink/_sql/input_entities.sql b/src/test/resources/edu/ie3/datamodel/io/sink/_sql/input_entities.sql index 31464bae39..f12754c102 100644 --- a/src/test/resources/edu/ie3/datamodel/io/sink/_sql/input_entities.sql +++ b/src/test/resources/edu/ie3/datamodel/io/sink/_sql/input_entities.sql @@ -41,7 +41,7 @@ CREATE TABLE public.evcs_input controlling_em UUID, cos_phi_rated TEXT NOT NULL, id TEXT NOT NULL, - location_type TEXT NOT NULL, + location_types TEXT NOT NULL, node UUID NOT NULL, operates_from TIMESTAMP WITH TIME ZONE, operates_until TIMESTAMP WITH TIME ZONE, diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_joint_grid/evcs_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_joint_grid/evcs_input.csv index 011aeb72a2..613d63b42f 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_joint_grid/evcs_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_joint_grid/evcs_input.csv @@ -1,3 +1,3 @@ -uuid,charging_points,cos_phi_rated,id,location_type,node,operates_from,operates_until,operator,q_characteristics,type,v_2g_support,controlling_em +uuid,charging_points,cos_phi_rated,id,location_types,node,operates_from,operates_until,operator,q_characteristics,type,v_2g_support,controlling_em 06a14909-366e-4e94-a593-1016e1455b30,4,0.9,test_evcs_1,HOME,5f1c776c-6935-40f7-ba9e-60646e08992b,,,,"cosPhiFixed:{(0.0,1.0)}",ChargingStationType1,true, 104acdaa-5dc5-4197-aed2-2fddb3c4f237,4,0.9,test_evcs_2,HOME,ed4697fd-016c-40c2-a66b-e793878dadea,,,,"cosPhiFixed:{(0.0,1.0)}",ChargingStationType1,true, diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/evcs_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/evcs_input.csv index 0fd01d91ff..ef408c5d2c 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/evcs_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/evcs_input.csv @@ -1,2 +1,2 @@ -uuid,id,operator,operates_from,operates_until,node,q_characteristics,cos_phi_rated,type,charging_points,location_type,v2g_support,controlling_em +uuid,id,operator,operates_from,operates_until,node,q_characteristics,cos_phi_rated,type,charging_points,location_types,v2g_support,controlling_em 798028b5-caff-4da7-bcd9-1750fdd8742c,test_csInput,8f9682df-0744-4b58-a122-f0dc730f6510,2020-03-24T15:11:31Z,2020-03-25T15:11:31Z,4ca90220-74c2-4369-9afa-a18bf068840d,"cosPhiFixed:{(0.00,0.95)}",0.95,hhs,4,HOME,false,977157f4-25e5-4c72-bf34-440edc778792 \ No newline at end of file From 06cc5d8e5b13295db24f7982329a5f13489dd76e Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 21 Oct 2025 21:57:41 +0200 Subject: [PATCH 2/6] fix parsing process in case of exported EvcsLocationTypes --- .../evcslocation/EvcsLocationTypeUtils.java | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtils.java b/src/main/java/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtils.java index 7cb4dc6019..bef5e83836 100644 --- a/src/main/java/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtils.java +++ b/src/main/java/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtils.java @@ -43,26 +43,10 @@ public static List parse(String parsableString) throws Parsing throw new ParsingException("Location types string cannot be null or empty"); } - if (parsableString.startsWith("[") && parsableString.endsWith("]")) { - String listContent = parsableString.substring(1, parsableString.length() - 1); - if (listContent.isEmpty()) { - return java.util.Collections.emptyList(); - } - return Arrays.stream(listContent.split(",")) - .map(String::trim) - .filter(s -> !s.isEmpty()) - .map( - s -> { - try { - return parseSingle(s); - } catch (ParsingException e) { - throw new RuntimeException(e); - } - }) - .collect(Collectors.toList()); - } + // Remove brackets if present + parsableString = parsableString.replace("[", "").replace("]", ""); - // Check if it contains comma for multiple values (without brackets) + // Check if it contains comma for multiple values if (parsableString.contains(",")) { return Arrays.stream(parsableString.split(",")) .map(String::trim) From 56888fb4a33b628081874dcc4c175e4c88068af2 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 21 Oct 2025 22:07:18 +0200 Subject: [PATCH 3/6] fix parsing process in case of exported EvcsLocationTypes also for single entries --- .../input/system/type/evcslocation/EvcsLocationTypeUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtils.java b/src/main/java/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtils.java index bef5e83836..60f6a97073 100644 --- a/src/main/java/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtils.java +++ b/src/main/java/edu/ie3/datamodel/models/input/system/type/evcslocation/EvcsLocationTypeUtils.java @@ -75,7 +75,7 @@ public static List parse(String parsableString) throws Parsing * @throws ParsingException if string does not represent a location type */ public static EvcsLocationType parseSingle(String parsableString) throws ParsingException { - final String key = toKey(parsableString); + final String key = toKey(parsableString.replace("[", "").replace("]", "")); if (nameToType.containsKey(key)) return nameToType.get(key); else throw new ParsingException("EvcsLocationType '" + key + "' does not exist."); } From 78fa0e120fbde32bdd9ba253ff710b9d866fb9a3 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Mon, 13 Apr 2026 14:03:06 +0200 Subject: [PATCH 4/6] fix after merging dev --- .../input/participant/EvcsInputFactory.java | 18 +++++++----------- .../io/naming/FieldNamingStrategy.java | 2 +- .../ie3/datamodel/io/naming/ModelFields.java | 3 ++- .../models/input/system/EvcsInput.java | 7 ++++--- .../participant/EvcsInputFactoryTest.groovy | 8 ++++---- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java index 3728e2b43e..b02c8b07ce 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java @@ -60,19 +60,15 @@ protected EvcsInput buildModel( final int chargingPoints = data.getInt(CHARGING_POINTS); final double cosPhi = data.getDouble(COS_PHI_RATED); - final EvcsLocationType locationTypes; + final List locationTypes; String locationFieldValue = data.getField(LOCATION_TYPES); try { - locationTypes = EvcsLocationTypeUtils.parse(locationFieldValue); - if (locationTypesField.contains(",")) { - locationTypes = EvcsLocationTypeUtils.parse(locationTypesField); - } else { - locationTypes = List.of(EvcsLocationTypeUtils.parseSingle(locationTypesField)); - } catch (ParsingException e) { - Throwable cause = - e instanceof RuntimeException && e.getCause() instanceof ParsingException - ? e.getCause() - : e; + if (locationFieldValue.contains(",")) { + locationTypes = EvcsLocationTypeUtils.parse(locationFieldValue); + } else { + locationTypes = List.of(EvcsLocationTypeUtils.parseSingle(locationFieldValue)); + } + } catch (ParsingException e) { throw new FactoryException( String.format( "Exception while trying to parse field \"%s\" with supposed int value \"%s\"", diff --git a/src/main/java/edu/ie3/datamodel/io/naming/FieldNamingStrategy.java b/src/main/java/edu/ie3/datamodel/io/naming/FieldNamingStrategy.java index 1a469a3725..7a90bc7aed 100644 --- a/src/main/java/edu/ie3/datamodel/io/naming/FieldNamingStrategy.java +++ b/src/main/java/edu/ie3/datamodel/io/naming/FieldNamingStrategy.java @@ -100,7 +100,7 @@ public class FieldNamingStrategy { public static final String COS_PHI_RATED = "cosPhiRated"; public static final String E_CONS_ANNUAL = "eConsAnnual"; public static final String FEED_IN_TARIFF = "feedInTariff"; - public static final String LOCATION_TYPE = "locationType"; + public static final String LOCATION_TYPES = "locationTypes"; public static final String LOAD_PROFILE = "loadProfile"; public static final String OLM_CHARACTERISTIC = "olmCharacteristic"; public static final String Q_CHARACTERISTICS = "qCharacteristics"; diff --git a/src/main/java/edu/ie3/datamodel/io/naming/ModelFields.java b/src/main/java/edu/ie3/datamodel/io/naming/ModelFields.java index fa67dcb07b..3c1e14dd2b 100644 --- a/src/main/java/edu/ie3/datamodel/io/naming/ModelFields.java +++ b/src/main/java/edu/ie3/datamodel/io/naming/ModelFields.java @@ -333,7 +333,8 @@ private static void registerParticipantFields() { addMandatory(ChpInput.class, TYPE, THERMAL_BUS, THERMAL_STORAGE); - addMandatory(EvcsInput.class, TYPE, CHARGING_POINTS, COS_PHI_RATED, LOCATION_TYPE, V2G_SUPPORT); + addMandatory( + EvcsInput.class, TYPE, CHARGING_POINTS, COS_PHI_RATED, LOCATION_TYPES, V2G_SUPPORT); addMandatory(EvInput.class, TYPE, Q_CHARACTERISTICS); diff --git a/src/main/java/edu/ie3/datamodel/models/input/system/EvcsInput.java b/src/main/java/edu/ie3/datamodel/models/input/system/EvcsInput.java index 1032ff4335..fee63a31f3 100644 --- a/src/main/java/edu/ie3/datamodel/models/input/system/EvcsInput.java +++ b/src/main/java/edu/ie3/datamodel/models/input/system/EvcsInput.java @@ -12,6 +12,7 @@ import edu.ie3.datamodel.models.input.system.characteristic.ReactivePowerCharacteristic; import edu.ie3.datamodel.models.input.system.type.chargingpoint.ChargingPointType; import edu.ie3.datamodel.models.input.system.type.evcslocation.EvcsLocationType; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -84,7 +85,7 @@ public EvcsInput( * @param type type of the charging points available to this charging station * @param chargingPoints number of charging points available at this charging station * @param cosPhiRated rated cos phi - * @param locationType the location type + * @param locationTypes the location types (minimum one required) * @param v2gSupport whether charging station supports vehicle to grid * @param additionalInformation That were provided by the source */ @@ -99,14 +100,14 @@ public EvcsInput( ChargingPointType type, int chargingPoints, double cosPhiRated, - EvcsLocationType locationType, + List locationTypes, boolean v2gSupport, Map additionalInformation) { super(uuid, id, operator, operationTime, node, qCharacteristics, em); this.type = type; this.chargingPoints = chargingPoints; this.cosPhiRated = cosPhiRated; - this.locationType = locationType; + this.locationTypes = locationTypes; this.v2gSupport = v2gSupport; setAdditionalInformation(additionalInformation); } diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy index ba359b97a4..0f6f72499b 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy @@ -49,8 +49,8 @@ class EvcsInputFactoryTest extends Specification implements FactoryTestHelper { "type" : "Household", "chargingpoints" : "4", "cosphirated" : "0.95", - "locationtypes" : "CHARGING_HUB_TOWN, STREET", - "v2gsupport" : "false" + "locationtypes" : "CHARGING_HUB_TOWN, STREET", + "v2gsupport" : "false" ] def inputClass = EvcsInput def nodeInput = Mock(NodeInput) @@ -132,8 +132,8 @@ class EvcsInputFactoryTest extends Specification implements FactoryTestHelper { "type" : "Household", "chargingpoints" : "4", "cosphirated" : "0.95", - "locationTypes" : "-- invalid --", - "v2gsupport" : "false" + "locationtypes" : "-- invalid --", + "v2gsupport" : "false" ] def inputClass = EvcsInput def nodeInput = Mock(NodeInput) From da83f9c1e93b654174146b27b8f02cf60af2dfcf Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Mon, 13 Apr 2026 14:03:47 +0200 Subject: [PATCH 5/6] fmt --- .../datamodel/models/input/system/EvcsInput.java | 13 +++++-------- .../input/participant/EvcsInputFactoryTest.groovy | 1 - 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/models/input/system/EvcsInput.java b/src/main/java/edu/ie3/datamodel/models/input/system/EvcsInput.java index fee63a31f3..c1a60694f9 100644 --- a/src/main/java/edu/ie3/datamodel/models/input/system/EvcsInput.java +++ b/src/main/java/edu/ie3/datamodel/models/input/system/EvcsInput.java @@ -38,7 +38,7 @@ public class EvcsInput extends SystemParticipantInput { /** * @param uuid Unique identifier - * @param id Human readable identifier + * @param id Human-readable identifier * @param operator of the asset * @param operationTime Time for which the entity is operated * @param node that the asset is connected to @@ -76,7 +76,7 @@ public EvcsInput( /** * @param uuid Unique identifier - * @param id Human readable identifier + * @param id Human-readable identifier * @param operator of the asset * @param operationTime Time for which the entity is operated * @param node that the asset is connected to @@ -114,7 +114,7 @@ public EvcsInput( /** * @param uuid Unique identifier - * @param id Human readable identifier + * @param id Human-readable identifier * @param operator of the asset * @param operationTime Time for which the entity is operated * @param node that the asset is connected to @@ -154,7 +154,7 @@ public EvcsInput( /** * @param uuid Unique identifier - * @param id Human readable identifier + * @param id Human-readable identifier * @param node that the asset is connected to * @param qCharacteristics Description of a reactive power characteristic * @param em The {@link EmInput} controlling this system participant. Null, if not applicable. @@ -188,7 +188,7 @@ public EvcsInput( /** * @param uuid Unique identifier - * @param id Human readable identifier + * @param id Human-readable identifier * @param node that the asset is connected to * @param qCharacteristics Description of a reactive power characteristic * @param em The {@link EmInput} controlling this system participant. Null, if not applicable. @@ -292,9 +292,6 @@ public String toString() { /** * A builder pattern based approach to create copies of {@link EvcsInput} entities with altered * field values. For detailed field descriptions refer to java docs of {@link EvcsInput} - * - * @version 0.1 - * @since 05.06.20 */ public static class EvcsInputCopyBuilder extends SystemParticipantInputCopyBuilder { diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy index 0f6f72499b..2bd7e6c7bf 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy @@ -24,7 +24,6 @@ import javax.measure.quantity.Dimensionless /** * Testing EvcsInputFactory * - * @version 0.1* @since 26.07.20 */ class EvcsInputFactoryTest extends Specification implements FactoryTestHelper { From 64103dda5104f9d2d8d4881be830e3867df32e77 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Mon, 13 Apr 2026 14:18:19 +0200 Subject: [PATCH 6/6] adapt tests --- .../input/participant/EvcsInputFactory.java | 5 +- .../participant/EvcsInputFactoryTest.groovy | 16 +++---- .../models/input/system/EvcsInputTest.groovy | 46 +++++++++---------- 3 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java index b02c8b07ce..4baf07602f 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java @@ -24,9 +24,6 @@ /** * Factory to create instances of {@link EvcsInput}s based on {@link SystemParticipantEntityData} * and additional fields. - * - * @version 0.1 - * @since 26.07.20 */ public class EvcsInputFactory extends SystemParticipantInputEntityFactory { @@ -71,7 +68,7 @@ protected EvcsInput buildModel( } catch (ParsingException e) { throw new FactoryException( String.format( - "Exception while trying to parse field \"%s\" with supposed int value \"%s\"", + "Exception while trying to parse field \"%s\" with supposed value \"%s\"", LOCATION_TYPES, locationFieldValue), e); } diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy index 2bd7e6c7bf..c451b37458 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactoryTest.groovy @@ -78,15 +78,15 @@ class EvcsInputFactoryTest extends Specification implements FactoryTestHelper { new CharacteristicPoint(Quantities.getQuantity(0d, PowerSystemUnits.PU), Quantities.getQuantity(1d, PowerSystemUnits.PU)) ] as TreeSet) } - assert controllingEm == Optional.of(emUnit) - assert type == ChargingPointTypeUtils.HouseholdSocket - assert chargingPoints == Integer.parseInt(parameter["chargingpoints"]) - assert cosPhiRated == Double.parseDouble(parameter["cosphirated"]) - assert locationTypes == [ + controllingEm == Optional.of(emUnit) + type == ChargingPointTypeUtils.HouseholdSocket + chargingPoints == Integer.parseInt(parameter["chargingpoints"]) + cosPhiRated == Double.parseDouble(parameter["cosphirated"]) + locationTypes == [ EvcsLocationType.CHARGING_HUB_TOWN, EvcsLocationType.STREET ] - assert !v2gSupport + !v2gSupport } } @@ -102,8 +102,8 @@ class EvcsInputFactoryTest extends Specification implements FactoryTestHelper { "type" : "-- invalid --", "chargingpoints" : "4", "cosphirated" : "0.95", - "locationtypes" : "[CHARGING_HUB_TOWN]", - "v2gsupport" : "false" + "locationtypes" : "[CHARGING_HUB_TOWN]", + "v2gsupport" : "false" ] def inputClass = EvcsInput def nodeInput = Mock(NodeInput) diff --git a/src/test/groovy/edu/ie3/datamodel/models/input/system/EvcsInputTest.groovy b/src/test/groovy/edu/ie3/datamodel/models/input/system/EvcsInputTest.groovy index 80be9e416f..33be996454 100644 --- a/src/test/groovy/edu/ie3/datamodel/models/input/system/EvcsInputTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/models/input/system/EvcsInputTest.groovy @@ -28,19 +28,19 @@ class EvcsInputTest extends Specification { then: alteredEntity.with { - assert uuid == evcsInput.uuid - assert operationTime == evcsInput.operationTime - assert operator == evcsInput.operator - assert id == evcsInput.id - assert qCharacteristics == evcsInput.qCharacteristics - assert type == ChargingPointTypeUtils.TeslaSuperChargerV3 - assert cosPhiRated == 0.7d - assert chargingPoints == 1 - assert locationTypes == [ + uuid == evcsInput.uuid + operationTime == evcsInput.operationTime + operator == evcsInput.operator + id == evcsInput.id + qCharacteristics == evcsInput.qCharacteristics + type == ChargingPointTypeUtils.TeslaSuperChargerV3 + cosPhiRated == 0.7d + chargingPoints == 1 + locationTypes == [ EvcsLocationType.CHARGING_HUB_HIGHWAY ] - assert v2gSupport - assert controllingEm == Optional.of(SystemParticipantTestData.emInput) + v2gSupport + controllingEm == Optional.of(SystemParticipantTestData.emInput) } } @@ -53,18 +53,18 @@ class EvcsInputTest extends Specification { then: alteredUnit.with { - assert uuid == evcsInput.uuid - assert operationTime == evcsInput.operationTime - assert operator == evcsInput.operator - assert id == evcsInput.id - assert qCharacteristics == evcsInput.qCharacteristics - assert type.sRated == evcsInput.type.sRated * 2d - assert sRated() == evcsInput.type.sRated * 2d - assert cosPhiRated == evcsInput.cosPhiRated - assert chargingPoints == evcsInput.chargingPoints - assert locationTypes == evcsInput.locationTypes - assert v2gSupport == evcsInput.v2gSupport - assert controllingEm == Optional.of(SystemParticipantTestData.emInput) + uuid == evcsInput.uuid + operationTime == evcsInput.operationTime + operator == evcsInput.operator + id == evcsInput.id + qCharacteristics == evcsInput.qCharacteristics + type.sRated == evcsInput.type.sRated * 2d + sRated() == evcsInput.type.sRated * 2d + cosPhiRated == evcsInput.cosPhiRated + chargingPoints == evcsInput.chargingPoints + locationTypes == evcsInput.locationTypes + v2gSupport == evcsInput.v2gSupport + controllingEm == Optional.of(SystemParticipantTestData.emInput) } } }