From 38c78e451eefe5c6037302f60ed4285ee03e8098 Mon Sep 17 00:00:00 2001 From: James Brown <64858662+james-d-brown@users.noreply.github.com> Date: Tue, 24 Dec 2024 14:51:06 +0000 Subject: [PATCH] Fully qualify the time windows associated with the legacy CSV file names when generating outputs by valid date pool, #130 --- .../ReginaOgdenEventDetectorTest.java | 68 +++++++++++++++- .../CommaSeparatedDiagramWriter.java | 78 +++++++++++-------- 2 files changed, 110 insertions(+), 36 deletions(-) diff --git a/wres-eventdetection/test/wres/eventdetection/ReginaOgdenEventDetectorTest.java b/wres-eventdetection/test/wres/eventdetection/ReginaOgdenEventDetectorTest.java index 6c89693fc8..5e42a0e387 100644 --- a/wres-eventdetection/test/wres/eventdetection/ReginaOgdenEventDetectorTest.java +++ b/wres-eventdetection/test/wres/eventdetection/ReginaOgdenEventDetectorTest.java @@ -127,7 +127,72 @@ void testDetectWithTrendAndOneEvent() Set expected = Set.of( TimeWindowOuter.of( expectedInner ) ); - // Assert that one event is detected + assertEquals( expected, actual ); + } + + @Test + void testDetectWithTrendAndThreeEvents() + { + TimeSeries testSeries = this.createTestSeries( false ); + + // Duplicate the series three times + Instant start = testSeries.getEvents() + .first() + .getTime(); + TimeSeries.Builder builder = new TimeSeries.Builder().setMetadata( testSeries.getMetadata() ); + for ( int i = 0; i < 3; i++ ) + { + for ( Event nextEvent : testSeries.getEvents() ) + { + Event adjusted = Event.of( start, nextEvent.getValue() ); + builder.addEvent( adjusted ); + start = start.plus( Duration.ofHours( 1 ) ); + } + } + + testSeries = builder.build(); + + EventDetectionParameters parameters = EventDetectionParametersBuilder.builder() + .windowSize( Duration.ofDays( 7 ) ) + .minimumEventDuration( Duration.ZERO ) + .halfLife( Duration.ofHours( 6 ) ) + .build(); + + ReginaOgdenEventDetector detector = ReginaOgdenEventDetector.of( parameters ); + + Set actual = detector.detect( testSeries ); + + Instant startOne = Instant.parse( "2018-01-22T02:00:00Z" ); + Instant endOne = Instant.parse( "2018-01-29T00:00:00Z" ); + + TimeWindow expectedInnerOne = MessageFactory.getTimeWindow() + .toBuilder() + .setEarliestValidTime( MessageFactory.getTimestamp( startOne ) ) + .setLatestValidTime( MessageFactory.getTimestamp( endOne ) ) + .build(); + + Instant startTwo = Instant.parse( "2018-03-04T18:00:00Z" ); + Instant endTwo = Instant.parse( "2018-03-11T16:00:00Z" ); + + TimeWindow expectedInnerTwo = MessageFactory.getTimeWindow() + .toBuilder() + .setEarliestValidTime( MessageFactory.getTimestamp( startTwo ) ) + .setLatestValidTime( MessageFactory.getTimestamp( endTwo ) ) + .build(); + + Instant startThree = Instant.parse( "2018-04-15T10:00:00Z" ); + Instant endThree = Instant.parse( "2018-04-22T08:00:00Z" ); + + TimeWindow expectedInnerThree = MessageFactory.getTimeWindow() + .toBuilder() + .setEarliestValidTime( MessageFactory.getTimestamp( startThree ) ) + .setLatestValidTime( MessageFactory.getTimestamp( endThree ) ) + .build(); + + Set expected = Set.of( TimeWindowOuter.of( expectedInnerOne), + TimeWindowOuter.of( expectedInnerTwo ), + TimeWindowOuter.of( expectedInnerThree ) ); + assertEquals( expected, actual ); } @@ -158,7 +223,6 @@ void testDetectWithTrendAndOneEventAndNoise() Set expected = Set.of( TimeWindowOuter.of( expectedInner ) ); - // Assert that one event is detected assertEquals( expected, actual ); } diff --git a/wres-writing/src/wres/writing/csv/statistics/CommaSeparatedDiagramWriter.java b/wres-writing/src/wres/writing/csv/statistics/CommaSeparatedDiagramWriter.java index 54e60f3a5d..b254df07ea 100644 --- a/wres-writing/src/wres/writing/csv/statistics/CommaSeparatedDiagramWriter.java +++ b/wres-writing/src/wres/writing/csv/statistics/CommaSeparatedDiagramWriter.java @@ -1,6 +1,5 @@ package wres.writing.csv.statistics; -import java.io.IOException; import java.nio.file.Path; import java.text.Format; import java.time.temporal.ChronoUnit; @@ -103,35 +102,28 @@ public Set apply( List statistics ) Format formatter = declaration.decimalFormat(); // Default, per time-window - try - { - // Group the statistics by the LRB context in which they appear. There will be one path written - // for each group (e.g., one path for each window with LeftOrRightOrBaseline.RIGHT data and one for - // each window with LeftOrRightOrBaseline.BASELINE data): #48287 - Map> groups = - Slicer.getGroupedStatistics( statistics ); + // Group the statistics by the LRB context in which they appear. There will be one path written + // for each group (e.g., one path for each window with LeftOrRightOrBaseline.RIGHT data and one for + // each window with LeftOrRightOrBaseline.BASELINE data): #48287 + Map> groups = + Slicer.getGroupedStatistics( statistics ); - for ( List nextGroup : groups.values() ) + for ( List nextGroup : groups.values() ) + { + // Slice by ensemble averaging type + List> sliced = + CommaSeparatedDiagramWriter.getSlicedStatistics( nextGroup ); + for ( List nextSlice : sliced ) { - // Slice by ensemble averaging type - List> sliced = - CommaSeparatedDiagramWriter.getSlicedStatistics( nextGroup ); - for ( List nextSlice : sliced ) - { - Set innerPathsWrittenTo = - CommaSeparatedDiagramWriter.writeOneDiagramOutputType( super.getOutputDirectory(), - super.getDeclaration(), - nextSlice, - formatter, - super.getDurationUnits() ); - paths.addAll( innerPathsWrittenTo ); - } + Set innerPathsWrittenTo = + CommaSeparatedDiagramWriter.writeOneDiagramOutputType( super.getOutputDirectory(), + declaration, + nextSlice, + formatter, + super.getDurationUnits() ); + paths.addAll( innerPathsWrittenTo ); } } - catch ( IOException e ) - { - throw new CommaSeparatedWriteException( "While writing comma separated output: ", e ); - } return Collections.unmodifiableSet( paths ); } @@ -140,11 +132,10 @@ public Set apply( List statistics ) * Writes all output for one diagram type. * * @param outputDirectory the directory into which to write - * @param declaration the project declaration + * @param declaration the declaration * @param output the diagram output * @param formatter optional formatter, can be null * @param durationUnits the time units for durations - * @throws IOException if the output cannot be written */ private static Set writeOneDiagramOutputType( Path outputDirectory, @@ -152,7 +143,6 @@ private static Set writeOneDiagramOutputType( Path outputDirectory, List output, Format formatter, ChronoUnit durationUnits ) - throws IOException { Set pathsWrittenTo = new HashSet<>( 1 ); @@ -173,7 +163,8 @@ private static Set writeOneDiagramOutputType( Path outputDirectory, Slicer.filter( output, m ), headerRow, formatter, - durationUnits ); + durationUnits, + declaration ); pathsWrittenTo.addAll( innerPathsWrittenTo ); @@ -191,6 +182,7 @@ private static Set writeOneDiagramOutputType( Path outputDirectory, * @param headerRow the header row * @param formatter optional formatter, can be null * @param durationUnits the time units for durations + * @param declaration the declaration * @return set of paths actually written to */ @@ -198,7 +190,8 @@ private static Set writeOneDiagramOutputTypePerTimeWindow( Path outputDire List output, StringJoiner headerRow, Format formatter, - ChronoUnit durationUnits ) + ChronoUnit durationUnits, + EvaluationDeclaration declaration ) { Set pathsWrittenTo = new HashSet<>( 1 ); @@ -208,7 +201,9 @@ private static Set writeOneDiagramOutputTypePerTimeWindow( Path outputDire for ( TimeWindowOuter timeWindow : timeWindows ) { List next = - Slicer.filter( output, data -> data.getPoolMetadata().getTimeWindow().equals( timeWindow ) ); + Slicer.filter( output, data -> data.getPoolMetadata() + .getTimeWindow() + .equals( timeWindow ) ); MetricConstants metricName = next.get( 0 ).getMetricName(); PoolMetadata meta = next.get( 0 ).getPoolMetadata(); @@ -222,7 +217,10 @@ private static Set writeOneDiagramOutputTypePerTimeWindow( Path outputDire headerRow ) ) ); // Write the output - String append = CommaSeparatedDiagramWriter.getPathQualifier( timeWindow, durationUnits, output ); + String append = CommaSeparatedDiagramWriter.getPathQualifier( timeWindow, + declaration, + durationUnits, + output ); Path outputPath = DataUtilities.getPathFromPoolMetadata( outputDirectory, meta, append, @@ -483,20 +481,32 @@ private static List> getSlicedStatistics( List statistics ) { + // Pooling windows + if ( Objects.nonNull( declaration.validDatePools() ) + || Objects.nonNull( declaration.eventDetection() ) + || !declaration.timePools() + .isEmpty() ) + { + return DataUtilities.toStringSafe( timeWindow, leadUnits ); + } + // Qualify all windows with the latest lead duration return DataUtilities.durationToNumericUnits( timeWindow.getLatestLeadDuration(), leadUnits ) + "_" - + leadUnits.name().toUpperCase() + + leadUnits.name() + .toUpperCase() + CommaSeparatedDiagramWriter.getEnsembleAverageQualifierString( statistics ); }