From b31e55d6aebe9a944a2c2bf4497f657b85836f05 Mon Sep 17 00:00:00 2001 From: Christian Biasuzzi Date: Tue, 19 Jul 2016 20:29:22 +0200 Subject: [PATCH 1/2] ref #15 - ENTSO-E filename convention --- .../eu/itesla_project/cases/CaseType.java | 31 ++++- .../cases/EntsoeCaseRepository.java | 56 +++++++- .../cases/EntsoeCaseRepositoryTest.java | 105 ++++++++++++++- .../ucte/util/UcteFileName.java | 55 ++++++-- .../ucte/util/UcteGeographicalCode.java | 4 +- .../ucte/util/UcteFileNameTest.java | 124 +++++++++++++++++- .../ucte/util/UcteGeographicalCodeTest.java | 3 + 7 files changed, 355 insertions(+), 23 deletions(-) diff --git a/case-repository/src/main/java/eu/itesla_project/cases/CaseType.java b/case-repository/src/main/java/eu/itesla_project/cases/CaseType.java index a50850fb..74b052dc 100644 --- a/case-repository/src/main/java/eu/itesla_project/cases/CaseType.java +++ b/case-repository/src/main/java/eu/itesla_project/cases/CaseType.java @@ -1,5 +1,6 @@ /** * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium) + * Copyright (c) 2016, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -12,5 +13,33 @@ */ public enum CaseType { SN, - FO + FO, + D2, + LT, + RE, + IDCF01, + IDCF02, + IDCF03, + IDCF04, + IDCF05, + IDCF06, + IDCF07, + IDCF08, + IDCF09, + IDCF10, + IDCF11, + IDCF12, + IDCF13, + IDCF14, + IDCF15, + IDCF16, + IDCF17, + IDCF18, + IDCF19, + IDCF20, + IDCF21, + IDCF22, + IDCF23; + } + diff --git a/case-repository/src/main/java/eu/itesla_project/cases/EntsoeCaseRepository.java b/case-repository/src/main/java/eu/itesla_project/cases/EntsoeCaseRepository.java index 884a2473..07bfd341 100644 --- a/case-repository/src/main/java/eu/itesla_project/cases/EntsoeCaseRepository.java +++ b/case-repository/src/main/java/eu/itesla_project/cases/EntsoeCaseRepository.java @@ -1,5 +1,6 @@ /** * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium) + * Copyright (c) 2016, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -112,13 +113,33 @@ private static Collection forCountryHacked(Country country .collect(Collectors.toList()); } + public static boolean isIntraday(CaseType ct) { + return ((ct != null) && (ct.name().startsWith("IDCF"))); + } + + public static String intraForecastDistanceInHoursSx(CaseType ct) { + return ct.name().substring(4,6); + } + private R scanRepository(DateTime date, CaseType type, Country country, Function, R> handler) { Collection geographicalCodes = country != null ? forCountryHacked(country) - : Collections.singleton(UcteGeographicalCode.UX); + : Arrays.asList(UcteGeographicalCode.UX, UcteGeographicalCode.UC); + + DateTime testDate1=date.minusHours(1); + String typeDirS=type.name(); + String typeID=type.name(); + if (isIntraday(type)) { + typeDirS = "IDCF"; + typeID = intraForecastDistanceInHoursSx(type); + } else if (type.equals(CaseType.D2)) { + typeDirS="2D"; // because enum names cannot be prefixed with a digit + typeID="2D"; + } + for (EntsoeFormat format : formats) { Path formatDir = config.getRootDir().resolve(format.getDirName()); if (Files.exists(formatDir)) { - Path typeDir = formatDir.resolve(type.name()); + Path typeDir = formatDir.resolve(typeDirS); if (Files.exists(typeDir)) { Path dayDir = typeDir.resolve(String.format("%04d", date.getYear())) .resolve(String.format("%02d", date.getMonthOfYear())) @@ -129,9 +150,12 @@ private R scanRepository(DateTime date, CaseType type, Country country, Func Collection forbiddenFormats = config.getForbiddenFormatsByGeographicalCode().get(geographicalCode); if (!forbiddenFormats.contains(format.getImporter().getFormat())) { for (int i = 9; i >= 0; i--) { - String baseName = String.format("%04d%02d%02d_%02d%02d_" + type + "%01d_" + geographicalCode.name() + "%01d", + String baseName = String.format("%04d%02d%02d_%02d%02d_" + typeID + "%01d_" + geographicalCode.name() + "%01d", date.getYear(), date.getMonthOfYear(), date.getDayOfMonth(), date.getHourOfDay(), date.getMinuteOfHour(), date.getDayOfWeek(), i); + if (testDate1.getHourOfDay() == date.getHourOfDay()) { + baseName = baseName.substring(0,9)+'B'+baseName.substring(10); + } ReadOnlyDataSource ds = dataSourceFactory.create(dayDir, baseName); if (importContexts == null) { importContexts = new ArrayList<>(); @@ -140,6 +164,15 @@ private R scanRepository(DateTime date, CaseType type, Country country, Func importContexts.add(new ImportContext(format.getImporter(), ds)); } } + if (importContexts.size()==0 ) { // for info purposes, only + String baseName1 = String.format("%04d%02d%02d_%02d%02d_" + typeID + "%01d_" + geographicalCode.name(), + date.getYear(), date.getMonthOfYear(), date.getDayOfMonth(), date.getHourOfDay(), date.getMinuteOfHour(), + date.getDayOfWeek()); + if (testDate1.getHourOfDay() == date.getHourOfDay()) { + baseName1 = baseName1.substring(0,9)+'B'+baseName1.substring(10); + } + LOGGER.warn("could not find any file {}[0-9] in directory {}", baseName1, dayDir); + } } } if (importContexts != null) { @@ -148,8 +181,14 @@ private R scanRepository(DateTime date, CaseType type, Country country, Func return result; } } + } else { + LOGGER.warn("could not find any (daydir) directory {}", dayDir); } + } else { + LOGGER.warn("could not find any (typedir) directory {}", typeDir); } + } else { + LOGGER.warn("could not find any (formatdir) directory {}", formatDir); } } return null; @@ -227,16 +266,25 @@ public Set dataAvailable(CaseType type, Set countries, Interv Set geographicalCodes = new HashSet<>(); if (countries == null) { geographicalCodes.add(UcteGeographicalCode.UX); + geographicalCodes.add(UcteGeographicalCode.UC); } else { for (Country country : countries) { geographicalCodes.addAll(forCountryHacked(country)); } } Multimap dates = HashMultimap.create(); + + String typeDirS=type.name(); + if (isIntraday(type)) { + typeDirS = "IDCF"; + } else if (type.equals(CaseType.D2)) { + typeDirS="2D"; // because enum names cannot be prefixed with a digit + } + for (EntsoeFormat format : formats) { Path formatDir = config.getRootDir().resolve(format.getDirName()); if (Files.exists(formatDir)) { - Path typeDir = formatDir.resolve(type.name()); + Path typeDir = formatDir.resolve(typeDirS); if (Files.exists(typeDir)) { browse(typeDir, path -> { UcteFileName ucteFileName = UcteFileName.parse(path.getFileName().toString()); diff --git a/case-repository/src/test/java/eu/itesla_project/cases/EntsoeCaseRepositoryTest.java b/case-repository/src/test/java/eu/itesla_project/cases/EntsoeCaseRepositoryTest.java index b370c324..583bb7ba 100644 --- a/case-repository/src/test/java/eu/itesla_project/cases/EntsoeCaseRepositoryTest.java +++ b/case-repository/src/test/java/eu/itesla_project/cases/EntsoeCaseRepositoryTest.java @@ -1,5 +1,6 @@ /** * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium) + * Copyright (c) 2016, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -31,9 +32,7 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumSet; +import java.util.*; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -152,6 +151,37 @@ public void setUp() throws Exception { createFile(dir4, "20130115_0015_SN2_D40.uct"); createFile(dir4, "20130115_0015_SN2_D70.uct"); createFile(dir4, "20130115_0015_SN2_D80.uct"); + + // D2 + Path dir5 = fileSystem.getPath("/UCT/2D/2013/01/15"); + Files.createDirectories(dir5); + createFile(dir5, "20130115_0030_2D2_FR0.uct"); + createFile(dir5, "20130115_0130_2D2_FR0.uct"); + + // LT + Path dir6 = fileSystem.getPath("/UCT/LT/2013/01/15"); + Files.createDirectories(dir6); + createFile(dir6, "20130115_0030_LT2_FR0.uct"); + createFile(dir6, "20130115_0130_LT2_FR0.uct"); + + // RE + Path dir7 = fileSystem.getPath("/UCT/RE/2013/01/15"); + Files.createDirectories(dir7); + createFile(dir7, "20130115_0030_RE2_FR0.uct"); + createFile(dir7, "20130115_0130_RE2_FR0.uct"); + + // INTRADAY + Path dir8 = fileSystem.getPath("/UCT/IDCF/2013/01/15"); + Files.createDirectories(dir8); + createFile(dir8, "20130115_0330_012_FR0.uct"); + createFile(dir8, "20130115_0330_022_FR0.uct"); + createFile(dir8, "20130115_0330_032_FR0.uct"); + + // daylight saving FO + Path dir9 = fileSystem.getPath("/UCT/FO/2016/10/30"); + Files.createDirectories(dir9); + createFile(dir9, "20161030_0230_FO7_FR0.uct"); + createFile(dir9, "20161030_B230_FO7_FR0.uct"); } @After @@ -169,7 +199,7 @@ public void testLoad() throws Exception { // check that cim network is loaded instead of uct network assertTrue(caseRepository.load(DateTime.parse("2013-01-14T00:15:00+01:00"), CaseType.SN, Country.FR).equals(Collections.singletonList(cimNetwork))); - // check that if cim is vorbidden for france, uct is loaded + // check that if cim is forbidden for france, uct is loaded caseRepository.getConfig().getForbiddenFormatsByGeographicalCode().put(UcteGeographicalCode.FR, "CIM1"); assertTrue(caseRepository.load(DateTime.parse("2013-01-14T00:15:00+01:00"), CaseType.SN, Country.FR).equals(Collections.singletonList(uctNetwork))); @@ -193,4 +223,71 @@ public void testDataAvailable() throws Exception { assertTrue(caseRepository.dataAvailable(CaseType.SN, EnumSet.of(Country.FR), Interval.parse("2013-01-14T00:00:00+01:00/2013-01-14T01:00:00+01:00")) .equals(Sets.newHashSet(DateTime.parse("2013-01-14T00:15:00+01:00"), DateTime.parse("2013-01-14T00:30:00+01:00")))); } + + @Test + public void testLoadD2() throws Exception { + assertTrue(caseRepository.load(DateTime.parse("2013-01-15T00:30:00+01:00"), CaseType.D2, Country.FR).size() == 1); + assertTrue(caseRepository.load(DateTime.parse("2013-01-15T00:45:00+01:00"), CaseType.D2, Country.FR).isEmpty()); + } + + @Test + public void testLoadLT() throws Exception { + assertTrue(caseRepository.load(DateTime.parse("2013-01-15T00:30:00+01:00"), CaseType.LT, Country.FR).size() == 1); + assertTrue(caseRepository.load(DateTime.parse("2013-01-15T00:45:00+01:00"), CaseType.LT, Country.FR).isEmpty()); + } + + @Test + public void testLoadRE() throws Exception { + assertTrue(caseRepository.load(DateTime.parse("2013-01-15T00:30:00+01:00"), CaseType.RE, Country.FR).size() == 1); + assertTrue(caseRepository.load(DateTime.parse("2013-01-15T00:45:00+01:00"), CaseType.RE, Country.FR).isEmpty()); + } + + @Test + public void testLoadDayLightSaving() throws Exception { + List networksCEST=caseRepository.load(DateTime.parse("2016-10-30T02:30:00+02:00"), CaseType.FO, Country.FR); + assertTrue(networksCEST.size() == 1); + List networksCET=caseRepository.load(DateTime.parse("2016-10-30T02:30:00+01:00"), CaseType.FO, Country.FR); + assertTrue(networksCET.size() == 1); + } + + @Test + public void testLoadIDCF() throws Exception { + assertTrue(caseRepository.load(DateTime.parse("2013-01-15T03:30:00+01:00"), CaseType.IDCF01, Country.FR).size() == 1); + assertTrue(caseRepository.load(DateTime.parse("2013-01-15T03:30:00+01:00"), CaseType.IDCF02, Country.FR).size() == 1); + assertTrue(caseRepository.load(DateTime.parse("2013-01-15T03:30:00+01:00"), CaseType.IDCF03, Country.FR).size() == 1); + assertTrue(caseRepository.load(DateTime.parse("2013-01-15T03:30:00+01:00"), CaseType.IDCF04, Country.FR).isEmpty()); + } + + @Test + public void testIsDataAvailable2D() throws Exception { + assertTrue(caseRepository.isDataAvailable(DateTime.parse("2013-01-15T00:30:00+01:00"), CaseType.D2, Country.FR)); + } + + @Test + public void testDataAvailable2D() throws Exception { + assertTrue(caseRepository.dataAvailable(CaseType.D2, EnumSet.of(Country.FR), Interval.parse("2013-01-15T00:00:00+01:00/2013-01-15T01:30:00+01:00")) + .equals(Sets.newHashSet(DateTime.parse("2013-01-15T00:30:00+01:00")))); + } + + @Test + public void testDataAvailableIntraday() throws Exception { + Set dset=caseRepository.dataAvailable(CaseType.IDCF01, EnumSet.of(Country.FR), Interval.parse("2013-01-15T00:00:00+01:00/2013-01-15T05:30:00+01:00")); + System.out.println(dset); + assertTrue(dset.equals(Sets.newHashSet(DateTime.parse("2013-01-15T03:30:00+01:00")))); + } + + @Test + public void testDataAvailableDayLightSaving() throws Exception { + + // double date CEST + CET + Set dset=caseRepository.dataAvailable(CaseType.FO, EnumSet.of(Country.FR), Interval.parse("2016-10-30T00:00:00+02:00/2016-10-30T03:30:00+01:00")); + System.out.println(dset); + assertTrue(dset.equals(Sets.newHashSet(DateTime.parse("2016-10-30T02:30:00+02:00"),DateTime.parse("2016-10-30T02:30:00+01:00")))); + + //just the CET one + dset=caseRepository.dataAvailable(CaseType.FO, EnumSet.of(Country.FR), Interval.parse("2016-10-30T02:30:00+01:00/2016-10-30T03:30:00+01:00")); + System.out.println(dset); + assertTrue(dset.equals(Sets.newHashSet(DateTime.parse("2016-10-30T02:30:00+01:00")))); + } + } \ No newline at end of file diff --git a/ucte-util/src/main/java/eu/itesla_project/ucte/util/UcteFileName.java b/ucte-util/src/main/java/eu/itesla_project/ucte/util/UcteFileName.java index d0be4784..fa84d532 100644 --- a/ucte-util/src/main/java/eu/itesla_project/ucte/util/UcteFileName.java +++ b/ucte-util/src/main/java/eu/itesla_project/ucte/util/UcteFileName.java @@ -1,5 +1,6 @@ /** * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium) + * Copyright (c) 2016, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -8,17 +9,20 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; +import org.joda.time.Interval; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static java.lang.Math.toIntExact; + /** * * @author Geoffroy Jamgotchian */ public class UcteFileName { - private static final Pattern DATE_REGEX = Pattern.compile(".*(\\d{4})[- /._]?(\\d{2})[- /._]?(\\d{2})[- /._]?(\\d{2})[- /._]?(\\d{2}).*"); + private static final Pattern DATE_REGEX = Pattern.compile("(\\d{4})(\\d{2})(\\d{2})_(B\\d|\\d{2})(\\d{2})_(\\w{2})(\\d)_(\\w{2})(\\d).*"); private final DateTime date; @@ -26,6 +30,11 @@ public class UcteFileName { private final UcteGeographicalCode geographicalCode; + private static long calcForecastDistance(DateTime date, int offset) { + return new Interval(date.withTimeAtStartOfDay(),date).toDuration().getStandardMinutes() + offset; + } + + public static UcteFileName parse(String str) { DateTime date = DateTime.now(); int forecastDistance = 0; @@ -36,21 +45,43 @@ public static UcteFileName parse(String str) { int year = Integer.parseInt(m.group(1)); int month = Integer.parseInt(m.group(2)); int dayOfMonth = Integer.parseInt(m.group(3)); - int hourOfDay = Integer.parseInt(m.group(4)); + String hourDayGroup = m.group(4); + boolean cestFlag=hourDayGroup.startsWith("B"); + int hourOfDay = (cestFlag==true) ? Integer.parseInt(m.group(4).substring(1)) : Integer.parseInt(m.group(4)); int minute = Integer.parseInt(m.group(5)); date = new DateTime(year, month, dayOfMonth, hourOfDay, minute, DateTimeZone.forID("Europe/Paris")); - - // extract horizon and forecast distance - if (str.contains("FO")) { - forecastDistance = 60 * (6 + hourOfDay) + minute; // DACF generated at 18:00 one day ahead7 - } else if (str.contains("SN")) { - forecastDistance = 0; + if (cestFlag) { + date = date.plusHours(1); + } + String fileType=m.group(6); + String dweekS=m.group(7); + String cCode=m.group(8); + String versionS=m.group(9); + + // extract forecast distance + switch (fileType) { + case "FO" : // Day ahead forecast + forecastDistance = toIntExact(calcForecastDistance(date, 6*60)); // DACF generated at 18:00 one day ahead7 + break; + case "2D" : // Two days ahead forecast + forecastDistance = toIntExact(calcForecastDistance(date, 29*60)); // D2CF generated at 19:00 one day ahead7 + break; + case "SN" : // Snapshot + forecastDistance = 0; + break; + case "RE" : //Reference //TODO forecastDistance + forecastDistance = 0; + break; + case "LR" : //Long Term Reference //TODO forecastDistance + forecastDistance = 0; + break; + default: //hh Intraday Forecasts, for the rest of the day: two-digits number is the forecast distance, in hours + forecastDistance = Integer.parseInt(m.group(6)) * 60; + break; } - } - if (str.length() >= 19) { - geographicalCode = UcteGeographicalCode.valueOf(str.substring(18, 20)); - } + geographicalCode = UcteGeographicalCode.valueOf(cCode); + } return new UcteFileName(date, forecastDistance, geographicalCode); } diff --git a/ucte-util/src/main/java/eu/itesla_project/ucte/util/UcteGeographicalCode.java b/ucte-util/src/main/java/eu/itesla_project/ucte/util/UcteGeographicalCode.java index 2d9313c2..20fcfa19 100644 --- a/ucte-util/src/main/java/eu/itesla_project/ucte/util/UcteGeographicalCode.java +++ b/ucte-util/src/main/java/eu/itesla_project/ucte/util/UcteGeographicalCode.java @@ -1,5 +1,6 @@ /** * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium) + * Copyright (c) 2016, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -34,7 +35,8 @@ public enum UcteGeographicalCode { PL(Country.PL), SI(Country.SI), NO(Country.NO), // not a real UCTE geo code but necessary to work with nordic - UX(null); + UX(null), + UC(null); private static Multimap COUNTRY_TO_GEOGRAPHICAL_CODES; diff --git a/ucte-util/src/test/java/eu/itesla_project/ucte/util/UcteFileNameTest.java b/ucte-util/src/test/java/eu/itesla_project/ucte/util/UcteFileNameTest.java index 4f9ea0b2..9b1f93de 100644 --- a/ucte-util/src/test/java/eu/itesla_project/ucte/util/UcteFileNameTest.java +++ b/ucte-util/src/test/java/eu/itesla_project/ucte/util/UcteFileNameTest.java @@ -1,14 +1,21 @@ /** * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium) + * Copyright (c) 2016, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package eu.itesla_project.ucte.util; +import eu.itesla_project.iidm.network.Country; import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.LocalDateTime; import org.junit.Test; +import java.util.ArrayList; +import java.util.List; + import static org.junit.Assert.*; /** @@ -33,4 +40,119 @@ public void testInvalidName() { assertTrue(ucteFileName.getForecastDistance() == 0); assertTrue(ucteFileName.getCountry() == null); } -} \ No newline at end of file + + @Test + public void testValidNameDaylightSaving() { + String fileName = "20161027_B230_SN7_D20"; + UcteFileName ucteFileName = UcteFileName.parse(fileName); + assertTrue(ucteFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+01:00"))); + assertTrue(ucteFileName.getForecastDistance() == 0); + assertTrue(ucteFileName.getCountry().equals("DE")); + assertTrue(ucteFileName.getGeographicalCode() == UcteGeographicalCode.D2); + } + + @Test + public void testValidName2DaysAhead() { + String fileName = "20161027_0230_2D7_D20"; + UcteFileName ucteFileName = UcteFileName.parse(fileName); + assertTrue(ucteFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+02:00"))); + assertTrue(ucteFileName.getForecastDistance() == 1890); + assertTrue(ucteFileName.getCountry().equals("DE")); + assertTrue(ucteFileName.getGeographicalCode() == UcteGeographicalCode.D2); + } + + @Test + public void testValidNameIntraday() { + String fileName = "20161027_0230_037_D20"; + UcteFileName ucteFileName = UcteFileName.parse(fileName); + assertTrue(ucteFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+02:00"))); + assertTrue(ucteFileName.getForecastDistance() == 3*60); + assertTrue(ucteFileName.getCountry().equals("DE")); + assertTrue(ucteFileName.getGeographicalCode() == UcteGeographicalCode.D2); + } + + @Test + public void testValidNameReference() { + String fileName = "20161027_0230_RE7_D20"; + UcteFileName ucteFileName = UcteFileName.parse(fileName); + assertTrue(ucteFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+02:00"))); + assertTrue(ucteFileName.getForecastDistance() == 0); //TODO + assertTrue(ucteFileName.getCountry().equals("DE")); + assertTrue(ucteFileName.getGeographicalCode() == UcteGeographicalCode.D2); + } + @Test + public void testValidNameLongTimeReference() { + String fileName = "20161027_0230_LR7_FR0"; + UcteFileName ucteFileName = UcteFileName.parse(fileName); + assertTrue(ucteFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+02:00"))); + assertTrue(ucteFileName.getForecastDistance() == 0); //TODO + assertTrue(ucteFileName.getCountry().equals("FR")); + assertTrue(ucteFileName.getGeographicalCode() == UcteGeographicalCode.FR); + } + + private static List getDateRange(DateTime start, DateTime end, int minutesStep) { + List ret = new ArrayList(); + DateTime tmp = start; + while (tmp.isBefore(end) || tmp.equals(end)) { + ret.add(tmp); + tmp = tmp.plusMinutes(minutesStep); + } + return ret; + } + + private static String toEntsoeFileName(DateTime date, String typeS, UcteGeographicalCode countryCode, int version, String fileSuffix) { + DateTime testDate1=date.minusHours(1); + String ret=String.format("%04d%02d%02d_%02d%02d_" + typeS + "%01d_" + countryCode.name() + "%01d" + "."+ fileSuffix, + date.getYear(), date.getMonthOfYear(), date.getDayOfMonth(), date.getHourOfDay(), date.getMinuteOfHour(), date.getDayOfWeek(), version); + if (testDate1.getHourOfDay() == date.getHourOfDay()) { + ret = ret.substring(0,9)+'B'+ret.substring(10); + } + return ret; + } + + + @Test + public void testDateRangeSnapshots() { + DateTimeZone parisZone=DateTimeZone.forID("Europe/Paris"); + + DateTime startDate= LocalDateTime.parse("2016-01-01T00:45:00.000").toDateTime(parisZone); + DateTime endDate=LocalDateTime.parse("2016-12-31T23:59:00.000").toDateTime(parisZone); + + // snapshots, generated each quarter of hour + final int[] idx = {0}; + getDateRange(startDate,endDate,15).stream().forEach(date -> { + String entsoeFilenameS=toEntsoeFileName(date,"SN",UcteGeographicalCode.FR,0, "uct"); + UcteFileName entsoeFilename=UcteFileName.parse(entsoeFilenameS); + assert(entsoeFilename.getForecastDistance() == 0); + assert(entsoeFilename.getCountry().equals(Country.FR.name())); + assert(entsoeFilename.getGeographicalCode().equals(UcteGeographicalCode.FR)); + assert(entsoeFilename.getDate().toString().equals(date.toString())); + idx[0]++; + }); + System.out.println("tested: " + idx[0] + " dates."); + + } + + @Test + public void testDateRangeForecasts() { + DateTimeZone parisZone=DateTimeZone.forID("Europe/Paris"); + + DateTime startDate=LocalDateTime.parse("2016-01-01T00:30:00.000").toDateTime(parisZone); + DateTime endDate=LocalDateTime.parse("2016-12-31T23:59:00.000").toDateTime(parisZone); + + // snapshots, generated each quarter of hour + final int[] idx = {0}; + getDateRange(startDate,endDate,30).stream().forEach(date -> { + String entsoeFilenameS=toEntsoeFileName(date,"FO",UcteGeographicalCode.D2, 0, "uct"); + UcteFileName entsoeFilename=UcteFileName.parse(entsoeFilenameS); + assertTrue(entsoeFilename.getCountry().equals(Country.DE.name())); + assertTrue(entsoeFilename.getGeographicalCode().equals(UcteGeographicalCode.D2)); + assertTrue(entsoeFilename.getDate().toString().equals(date.toString())); + idx[0]++; + }); + System.out.println("tested: " + idx[0] + " dates."); + + } + + +} diff --git a/ucte-util/src/test/java/eu/itesla_project/ucte/util/UcteGeographicalCodeTest.java b/ucte-util/src/test/java/eu/itesla_project/ucte/util/UcteGeographicalCodeTest.java index d71216f2..7afdae4b 100644 --- a/ucte-util/src/test/java/eu/itesla_project/ucte/util/UcteGeographicalCodeTest.java +++ b/ucte-util/src/test/java/eu/itesla_project/ucte/util/UcteGeographicalCodeTest.java @@ -1,5 +1,6 @@ /** * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium) + * Copyright (c) 2016, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -22,5 +23,7 @@ public void testForCountry() throws Exception { assertTrue(UcteGeographicalCode.forCountry(Country.FR).equals(Sets.newHashSet(UcteGeographicalCode.FR))); assertTrue(UcteGeographicalCode.forCountry(Country.DE).equals(Sets.newHashSet( UcteGeographicalCode.D1, UcteGeographicalCode.D2, UcteGeographicalCode.D4, UcteGeographicalCode.D7, UcteGeographicalCode.D8))); + assertTrue(UcteGeographicalCode.forCountry(null).equals(Sets.newHashSet( + UcteGeographicalCode.UX, UcteGeographicalCode.UC))); } } \ No newline at end of file From a286c45286ba1e31f06d2b1e07dcfa732215b86a Mon Sep 17 00:00:00 2001 From: Christian Biasuzzi Date: Tue, 26 Jul 2016 15:12:52 +0200 Subject: [PATCH 2/2] renames class UcteFileName to EntsoeFileName --- .../cases/EntsoeCaseRepository.java | 10 +-- .../iidm/import_/cim1/CIM1Converter.java | 8 +-- .../iidm/import_/ucte/UcteImporter.java | 24 +++---- ...{UcteFileName.java => EntsoeFileName.java} | 8 +-- ...eNameTest.java => EntsoeFileNameTest.java} | 72 +++++++++---------- 5 files changed, 61 insertions(+), 61 deletions(-) rename ucte-util/src/main/java/eu/itesla_project/ucte/util/{UcteFileName.java => EntsoeFileName.java} (93%) rename ucte-util/src/test/java/eu/itesla_project/ucte/util/{UcteFileNameTest.java => EntsoeFileNameTest.java} (61%) diff --git a/case-repository/src/main/java/eu/itesla_project/cases/EntsoeCaseRepository.java b/case-repository/src/main/java/eu/itesla_project/cases/EntsoeCaseRepository.java index 07bfd341..1e60c4f4 100644 --- a/case-repository/src/main/java/eu/itesla_project/cases/EntsoeCaseRepository.java +++ b/case-repository/src/main/java/eu/itesla_project/cases/EntsoeCaseRepository.java @@ -17,7 +17,7 @@ import eu.itesla_project.iidm.import_.Importers; import eu.itesla_project.iidm.network.Country; import eu.itesla_project.iidm.network.Network; -import eu.itesla_project.ucte.util.UcteFileName; +import eu.itesla_project.ucte.util.EntsoeFileName; import eu.itesla_project.ucte.util.UcteGeographicalCode; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -287,12 +287,12 @@ public Set dataAvailable(CaseType type, Set countries, Interv Path typeDir = formatDir.resolve(typeDirS); if (Files.exists(typeDir)) { browse(typeDir, path -> { - UcteFileName ucteFileName = UcteFileName.parse(path.getFileName().toString()); - UcteGeographicalCode geographicalCode = ucteFileName.getGeographicalCode(); + EntsoeFileName entsoeFileName = EntsoeFileName.parse(path.getFileName().toString()); + UcteGeographicalCode geographicalCode = entsoeFileName.getGeographicalCode(); if (geographicalCode != null && !config.getForbiddenFormatsByGeographicalCode().get(geographicalCode).contains(format.getImporter().getFormat()) - && interval.contains(ucteFileName.getDate())) { - dates.put(ucteFileName.getDate(), geographicalCode); + && interval.contains(entsoeFileName.getDate())) { + dates.put(entsoeFileName.getDate(), geographicalCode); } }); } diff --git a/cim1-import/src/main/java/eu/itesla_project/iidm/import_/cim1/CIM1Converter.java b/cim1-import/src/main/java/eu/itesla_project/iidm/import_/cim1/CIM1Converter.java index b15e74e7..f789ce60 100644 --- a/cim1-import/src/main/java/eu/itesla_project/iidm/import_/cim1/CIM1Converter.java +++ b/cim1-import/src/main/java/eu/itesla_project/iidm/import_/cim1/CIM1Converter.java @@ -10,7 +10,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimap; import eu.itesla_project.iidm.network.*; -import eu.itesla_project.ucte.util.UcteFileName; +import eu.itesla_project.ucte.util.EntsoeFileName; import org.jgrapht.UndirectedGraph; import org.jgrapht.alg.ConnectivityInspector; import org.jgrapht.graph.Pseudograph; @@ -1191,11 +1191,11 @@ Network convert() { } } - UcteFileName ucteFileName = UcteFileName.parse(fileName); + EntsoeFileName entsoeFileName = EntsoeFileName.parse(fileName); Network network = NetworkFactory.create(fileName, FORMAT); - network.setCaseDate(ucteFileName.getDate()); - network.setForecastDistance(ucteFileName.getForecastDistance()); + network.setCaseDate(entsoeFileName.getDate()); + network.setForecastDistance(entsoeFileName.getForecastDistance()); // Ends of transformers need to be in the same substation in the IIDM model, so check that a mapping is // not needed diff --git a/ucte-import/src/main/java/eu/itesla_project/iidm/import_/ucte/UcteImporter.java b/ucte-import/src/main/java/eu/itesla_project/iidm/import_/ucte/UcteImporter.java index 61869824..fb5ace7c 100644 --- a/ucte-import/src/main/java/eu/itesla_project/iidm/import_/ucte/UcteImporter.java +++ b/ucte-import/src/main/java/eu/itesla_project/iidm/import_/ucte/UcteImporter.java @@ -17,7 +17,7 @@ import eu.itesla_project.ucte.network.ext.UcteSubstation; import eu.itesla_project.ucte.network.ext.UcteVoltageLevel; import eu.itesla_project.ucte.network.io.UcteReader; -import eu.itesla_project.ucte.util.UcteFileName; +import eu.itesla_project.ucte.util.EntsoeFileName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,7 +71,7 @@ private static float getSusceptance(UcteElement ucteElement) { return b; } - private static void createBuses(UcteNetworkExt ucteNetwork, Network network, UcteFileName ucteFileName) { + private static void createBuses(UcteNetworkExt ucteNetwork, Network network, EntsoeFileName entsoeFileName) { for (UcteSubstation ucteSubstation : ucteNetwork.getSubstations()) { // skip substations with only one Xnode @@ -499,11 +499,11 @@ private static void createPhaseTapChanger(UcteAngleRegulation ucteAngleRegulatio private static TwoWindingsTransformer createXnodeTransfo(UcteNetworkExt ucteNetwork, UcteTransformer ucteTransfo, boolean connected, UcteNodeCode xNodeCode, UcteNodeCode ucteOtherNodeCode, UcteVoltageLevel ucteOtherVoltageLevel, - Substation substation, UcteFileName ucteFileName) { + Substation substation, EntsoeFileName entsoeFileName) { // transfo connected to a XNODE, create an intermediate YNODE and small impedance line // otherNode--transfo--XNODE => otherNode--transfo--YNODE--line--XNODE String xNodeName = xNodeCode.toString(); - String yNodeName = ucteFileName.getCountry() != null ? ucteFileName.getCountry() + "_" + xNodeName : "YNODE_" + xNodeName; + String yNodeName = entsoeFileName.getCountry() != null ? entsoeFileName.getCountry() + "_" + xNodeName : "YNODE_" + xNodeName; VoltageLevel yVoltageLevel = substation.newVoltageLevel() .setId(yNodeName + "_VL") @@ -583,7 +583,7 @@ private static TwoWindingsTransformer createXnodeTransfo(UcteNetworkExt ucteNetw .add(); } - private static void createTransformers(UcteNetworkExt ucteNetwork, Network network, UcteFileName ucteFileName) { + private static void createTransformers(UcteNetworkExt ucteNetwork, Network network, EntsoeFileName entsoeFileName) { for (UcteTransformer ucteTransfo : ucteNetwork.getTransformers()) { UcteNodeCode nodeCode1 = ucteTransfo.getId().getNodeCode1(); UcteNodeCode nodeCode2 = ucteTransfo.getId().getNodeCode2(); @@ -615,12 +615,12 @@ private static void createTransformers(UcteNetworkExt ucteNetwork, Network netwo if (nodeCode1.getUcteCountryCode() == UcteCountryCode.XX && nodeCode2.getUcteCountryCode() != UcteCountryCode.XX) { // transformer connected to XNODE - transformer = createXnodeTransfo(ucteNetwork, ucteTransfo, connected, nodeCode1, nodeCode2, ucteVoltageLevel2, substation, ucteFileName); + transformer = createXnodeTransfo(ucteNetwork, ucteTransfo, connected, nodeCode1, nodeCode2, ucteVoltageLevel2, substation, entsoeFileName); } else if (nodeCode2.getUcteCountryCode() == UcteCountryCode.XX && nodeCode1.getUcteCountryCode() != UcteCountryCode.XX) { // transformer connected to XNODE - transformer = createXnodeTransfo(ucteNetwork, ucteTransfo, connected, nodeCode2, nodeCode1, ucteVoltageLevel1, substation, ucteFileName); + transformer = createXnodeTransfo(ucteNetwork, ucteTransfo, connected, nodeCode2, nodeCode1, ucteVoltageLevel1, substation, entsoeFileName); } else { // standard transformer @@ -697,15 +697,15 @@ public Network import_(ReadOnlyDataSource dataSource, Properties parameters) { UcteNetworkExt ucteNetwork = new UcteNetworkExt(new UcteReader().read(reader), LINE_MIN_Z); String fileName = dataSource.getBaseName(); - UcteFileName ucteFileName = UcteFileName.parse(fileName); + EntsoeFileName entsoeFileName = EntsoeFileName.parse(fileName); Network network = NetworkFactory.create(fileName, "UCTE"); - network.setCaseDate(ucteFileName.getDate()); - network.setForecastDistance(ucteFileName.getForecastDistance()); + network.setCaseDate(entsoeFileName.getDate()); + network.setForecastDistance(entsoeFileName.getForecastDistance()); - createBuses(ucteNetwork, network, ucteFileName); + createBuses(ucteNetwork, network, entsoeFileName); createLines(ucteNetwork, network); - createTransformers(ucteNetwork, network, ucteFileName); + createTransformers(ucteNetwork, network, entsoeFileName); LOGGER.debug("UCTE import done in {} ms", (System.currentTimeMillis() - start)); return network; } diff --git a/ucte-util/src/main/java/eu/itesla_project/ucte/util/UcteFileName.java b/ucte-util/src/main/java/eu/itesla_project/ucte/util/EntsoeFileName.java similarity index 93% rename from ucte-util/src/main/java/eu/itesla_project/ucte/util/UcteFileName.java rename to ucte-util/src/main/java/eu/itesla_project/ucte/util/EntsoeFileName.java index fa84d532..da677e97 100644 --- a/ucte-util/src/main/java/eu/itesla_project/ucte/util/UcteFileName.java +++ b/ucte-util/src/main/java/eu/itesla_project/ucte/util/EntsoeFileName.java @@ -20,7 +20,7 @@ * * @author Geoffroy Jamgotchian */ -public class UcteFileName { +public class EntsoeFileName { private static final Pattern DATE_REGEX = Pattern.compile("(\\d{4})(\\d{2})(\\d{2})_(B\\d|\\d{2})(\\d{2})_(\\w{2})(\\d)_(\\w{2})(\\d).*"); @@ -35,7 +35,7 @@ private static long calcForecastDistance(DateTime date, int offset) { } - public static UcteFileName parse(String str) { + public static EntsoeFileName parse(String str) { DateTime date = DateTime.now(); int forecastDistance = 0; UcteGeographicalCode geographicalCode = null; @@ -82,10 +82,10 @@ public static UcteFileName parse(String str) { geographicalCode = UcteGeographicalCode.valueOf(cCode); } - return new UcteFileName(date, forecastDistance, geographicalCode); + return new EntsoeFileName(date, forecastDistance, geographicalCode); } - private UcteFileName(DateTime date, int forecastDistance, UcteGeographicalCode geographicalCode) { + private EntsoeFileName(DateTime date, int forecastDistance, UcteGeographicalCode geographicalCode) { this.date = date; this.forecastDistance = forecastDistance; this.geographicalCode = geographicalCode; diff --git a/ucte-util/src/test/java/eu/itesla_project/ucte/util/UcteFileNameTest.java b/ucte-util/src/test/java/eu/itesla_project/ucte/util/EntsoeFileNameTest.java similarity index 61% rename from ucte-util/src/test/java/eu/itesla_project/ucte/util/UcteFileNameTest.java rename to ucte-util/src/test/java/eu/itesla_project/ucte/util/EntsoeFileNameTest.java index 9b1f93de..ca39f5bc 100644 --- a/ucte-util/src/test/java/eu/itesla_project/ucte/util/UcteFileNameTest.java +++ b/ucte-util/src/test/java/eu/itesla_project/ucte/util/EntsoeFileNameTest.java @@ -21,73 +21,73 @@ /** * @author Geoffroy Jamgotchian */ -public class UcteFileNameTest { +public class EntsoeFileNameTest { @Test public void testValidName() { String fileName = "20140213_0830_SN4_D20"; - UcteFileName ucteFileName = UcteFileName.parse(fileName); - assertTrue(ucteFileName.getDate().isEqual(DateTime.parse("2014-02-13T08:30:00.000+01:00"))); - assertTrue(ucteFileName.getForecastDistance() == 0); - assertTrue(ucteFileName.getCountry().equals("DE")); - assertTrue(ucteFileName.getGeographicalCode() == UcteGeographicalCode.D2); + EntsoeFileName entsoeFileName = EntsoeFileName.parse(fileName); + assertTrue(entsoeFileName.getDate().isEqual(DateTime.parse("2014-02-13T08:30:00.000+01:00"))); + assertTrue(entsoeFileName.getForecastDistance() == 0); + assertTrue(entsoeFileName.getCountry().equals("DE")); + assertTrue(entsoeFileName.getGeographicalCode() == UcteGeographicalCode.D2); } @Test public void testInvalidName() { String fileName = "???"; - UcteFileName ucteFileName = UcteFileName.parse(fileName); - assertTrue(ucteFileName.getForecastDistance() == 0); - assertTrue(ucteFileName.getCountry() == null); + EntsoeFileName entsoeFileName = EntsoeFileName.parse(fileName); + assertTrue(entsoeFileName.getForecastDistance() == 0); + assertTrue(entsoeFileName.getCountry() == null); } @Test public void testValidNameDaylightSaving() { String fileName = "20161027_B230_SN7_D20"; - UcteFileName ucteFileName = UcteFileName.parse(fileName); - assertTrue(ucteFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+01:00"))); - assertTrue(ucteFileName.getForecastDistance() == 0); - assertTrue(ucteFileName.getCountry().equals("DE")); - assertTrue(ucteFileName.getGeographicalCode() == UcteGeographicalCode.D2); + EntsoeFileName entsoeFileName = EntsoeFileName.parse(fileName); + assertTrue(entsoeFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+01:00"))); + assertTrue(entsoeFileName.getForecastDistance() == 0); + assertTrue(entsoeFileName.getCountry().equals("DE")); + assertTrue(entsoeFileName.getGeographicalCode() == UcteGeographicalCode.D2); } @Test public void testValidName2DaysAhead() { String fileName = "20161027_0230_2D7_D20"; - UcteFileName ucteFileName = UcteFileName.parse(fileName); - assertTrue(ucteFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+02:00"))); - assertTrue(ucteFileName.getForecastDistance() == 1890); - assertTrue(ucteFileName.getCountry().equals("DE")); - assertTrue(ucteFileName.getGeographicalCode() == UcteGeographicalCode.D2); + EntsoeFileName entsoeFileName = EntsoeFileName.parse(fileName); + assertTrue(entsoeFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+02:00"))); + assertTrue(entsoeFileName.getForecastDistance() == 1890); + assertTrue(entsoeFileName.getCountry().equals("DE")); + assertTrue(entsoeFileName.getGeographicalCode() == UcteGeographicalCode.D2); } @Test public void testValidNameIntraday() { String fileName = "20161027_0230_037_D20"; - UcteFileName ucteFileName = UcteFileName.parse(fileName); - assertTrue(ucteFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+02:00"))); - assertTrue(ucteFileName.getForecastDistance() == 3*60); - assertTrue(ucteFileName.getCountry().equals("DE")); - assertTrue(ucteFileName.getGeographicalCode() == UcteGeographicalCode.D2); + EntsoeFileName entsoeFileName = EntsoeFileName.parse(fileName); + assertTrue(entsoeFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+02:00"))); + assertTrue(entsoeFileName.getForecastDistance() == 3*60); + assertTrue(entsoeFileName.getCountry().equals("DE")); + assertTrue(entsoeFileName.getGeographicalCode() == UcteGeographicalCode.D2); } @Test public void testValidNameReference() { String fileName = "20161027_0230_RE7_D20"; - UcteFileName ucteFileName = UcteFileName.parse(fileName); - assertTrue(ucteFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+02:00"))); - assertTrue(ucteFileName.getForecastDistance() == 0); //TODO - assertTrue(ucteFileName.getCountry().equals("DE")); - assertTrue(ucteFileName.getGeographicalCode() == UcteGeographicalCode.D2); + EntsoeFileName entsoeFileName = EntsoeFileName.parse(fileName); + assertTrue(entsoeFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+02:00"))); + assertTrue(entsoeFileName.getForecastDistance() == 0); //TODO + assertTrue(entsoeFileName.getCountry().equals("DE")); + assertTrue(entsoeFileName.getGeographicalCode() == UcteGeographicalCode.D2); } @Test public void testValidNameLongTimeReference() { String fileName = "20161027_0230_LR7_FR0"; - UcteFileName ucteFileName = UcteFileName.parse(fileName); - assertTrue(ucteFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+02:00"))); - assertTrue(ucteFileName.getForecastDistance() == 0); //TODO - assertTrue(ucteFileName.getCountry().equals("FR")); - assertTrue(ucteFileName.getGeographicalCode() == UcteGeographicalCode.FR); + EntsoeFileName entsoeFileName = EntsoeFileName.parse(fileName); + assertTrue(entsoeFileName.getDate().isEqual(DateTime.parse("2016-10-27T02:30:00.000+02:00"))); + assertTrue(entsoeFileName.getForecastDistance() == 0); //TODO + assertTrue(entsoeFileName.getCountry().equals("FR")); + assertTrue(entsoeFileName.getGeographicalCode() == UcteGeographicalCode.FR); } private static List getDateRange(DateTime start, DateTime end, int minutesStep) { @@ -122,7 +122,7 @@ public void testDateRangeSnapshots() { final int[] idx = {0}; getDateRange(startDate,endDate,15).stream().forEach(date -> { String entsoeFilenameS=toEntsoeFileName(date,"SN",UcteGeographicalCode.FR,0, "uct"); - UcteFileName entsoeFilename=UcteFileName.parse(entsoeFilenameS); + EntsoeFileName entsoeFilename= EntsoeFileName.parse(entsoeFilenameS); assert(entsoeFilename.getForecastDistance() == 0); assert(entsoeFilename.getCountry().equals(Country.FR.name())); assert(entsoeFilename.getGeographicalCode().equals(UcteGeographicalCode.FR)); @@ -144,7 +144,7 @@ public void testDateRangeForecasts() { final int[] idx = {0}; getDateRange(startDate,endDate,30).stream().forEach(date -> { String entsoeFilenameS=toEntsoeFileName(date,"FO",UcteGeographicalCode.D2, 0, "uct"); - UcteFileName entsoeFilename=UcteFileName.parse(entsoeFilenameS); + EntsoeFileName entsoeFilename= EntsoeFileName.parse(entsoeFilenameS); assertTrue(entsoeFilename.getCountry().equals(Country.DE.name())); assertTrue(entsoeFilename.getGeographicalCode().equals(UcteGeographicalCode.D2)); assertTrue(entsoeFilename.getDate().toString().equals(date.toString()));