From 990aae3ae96cb35206d9d1161adabbe6b4d3b135 Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Wed, 14 Aug 2024 17:02:23 +0200
Subject: [PATCH 01/15] =?UTF-8?q?Ajouter=20une=20m=C3=A9thode=20pour=20cal?=
 =?UTF-8?q?culer=20toutes=20les=20valeurs=20correspondant=20au=20jeu=20cli?=
 =?UTF-8?q?matique=20fourni.=20refs=20agroclim/agrometinfo/AgroMetInfo=5F2?=
 =?UTF-8?q?.0#35?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../agroclim/indicators/model/Evaluation.java | 163 ++++++++++--------
 .../model/indicator/CompositeIndicator.java   |  10 +-
 .../indicators/model/indicator/Indicator.java |  14 +-
 .../model/EvaluationHourlyTest.java           |  12 +-
 .../model/EvaluationRobertTest.java           |  12 +-
 .../indicators/model/EvaluationTest.java      |  51 ++++--
 .../model/EvalutationCustomHeadersTest.java   |  12 +-
 .../indicators/model/MMarjouTest.java         |  14 +-
 .../indicators/model/RaidayMeantTest.java     |  45 +++--
 9 files changed, 188 insertions(+), 145 deletions(-)

diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
index 8e78e9e4..31baba23 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
@@ -16,8 +16,6 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
-import java.io.IOException;
-import java.io.ObjectInputStream;
 import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -106,21 +104,6 @@ public final class Evaluation extends CompositeIndicator {
         });
     }
 
-    /**
-     * Computed phases (or phases from file) used to compute indicators.
-     *
-     * TODO : supprimer et utiliser
-     * EvaluationResult::getPhaseResults::getAnnualPhase.
-     */
-    @Getter
-    private transient List<AnnualPhase> computedPhases;
-
-    /**
-     * Results of computation by year.
-     */
-    @Getter
-    private transient Map<Integer, EvaluationResult> results = new LinkedHashMap<>();
-
     /**
      * All resources needed to run the evaluation.
      *
@@ -129,6 +112,11 @@ public final class Evaluation extends CompositeIndicator {
     @Getter
     private final ResourceManager resourceManager;
 
+    /**
+     * Flag to ignore when climatic data is empty for a phase.
+     */
+    private boolean ignoreEmptyClimaticData = false;
+
     /**
      * Flag for state Saved.
      */
@@ -198,12 +186,7 @@ public final class Evaluation extends CompositeIndicator {
                 throw new RuntimeException("This should never occur!", ex);
             }
         }
-        if (evaluation.computedPhases != null) {
-            computedPhases = new ArrayList<>();
-            computedPhases.addAll(evaluation.computedPhases);
-        }
         isTranscient = evaluation.isTranscient;
-        results = evaluation.results;
         state = evaluation.state;
     }
 
@@ -285,11 +268,26 @@ public final class Evaluation extends CompositeIndicator {
     }
 
     /**
-     * Clear results of indicators.
+     * Check data before running compute* methods.
+     *
+     * @param phases phenological phases to check
+     * @param climaticResource climatic resource to check
+     * @throws IndicatorsException exception
      */
-    private void clearResults() {
-        results.clear();
-        computedPhases = new ArrayList<>();
+    private void checkBeforeCompute(final List<CompositeIndicator> phases, final ClimaticResource climaticResource)
+            throws IndicatorsException {
+        if (phases == null) {
+            throw new RuntimeException("Phase list is null!");
+        }
+        if (phases.isEmpty()) {
+            throw new RuntimeException("Phase list is empty!");
+        }
+        if (climaticResource.isEmpty()) {
+            throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY);
+        }
+        if (resourceManager.getPhenologicalResource().isEmpty()) {
+            throw new IndicatorsException(ResourceErrorType.PHENO_EMPTY);
+        }
     }
 
     @Override
@@ -300,32 +298,30 @@ public final class Evaluation extends CompositeIndicator {
     /**
      * Compute indicator results.
      *
+     * @return Results of computation by year.
      * @throws IndicatorsException
      *             from Indicator.compute()
      */
-    public void compute() throws IndicatorsException {
+    public Map<Integer, EvaluationResult> compute() throws IndicatorsException {
         LOGGER.trace("start computing evaluation \"" + getName() + "\"");
+        this.ignoreEmptyClimaticData = false;
         fireIndicatorEvent(IndicatorEvent.Type.COMPUTE_START.event(this));
 
         final List<CompositeIndicator> phases = getPhases();
-        if (phases == null) {
-            throw new RuntimeException("Phase list is null!");
-        }
-        if (phases.isEmpty()) {
-            throw new RuntimeException("Phase list is empty!");
-        }
-        if (resourceManager.getClimaticResource().isEmpty()) {
-            throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY);
-        }
-        if (resourceManager.getPhenologicalResource().isEmpty()) {
-            throw new IndicatorsException(ResourceErrorType.PHENO_EMPTY);
-        }
+        final ClimaticResource climaticResource = resourceManager.getClimaticResource();
+        checkBeforeCompute(phases, climaticResource);
+
+        var results = compute(climaticResource, phases);
+        fireIndicatorEvent(IndicatorEvent.Type.COMPUTE_SUCCESS.event(this));
+        return results;
+    }
 
-        clearResults();
+    private Map<Integer, EvaluationResult> compute(final ClimaticResource climaticResource,
+            final List<CompositeIndicator> phases) throws IndicatorsException {
+        final Map<Integer, EvaluationResult> results = new LinkedHashMap<>();
 
         /* Pour chaque phase */
-        List<AnnualStageData> stageDatas;
-        stageDatas = getResourceManager().getPhenologicalResource().getData();
+        final List<AnnualStageData> stageDatas = getResourceManager().getPhenologicalResource().getData();
         for (final CompositeIndicator phase : phases) {
             final String phaseId = phase.getId();
             if (phaseId == null) {
@@ -348,8 +344,7 @@ public final class Evaluation extends CompositeIndicator {
                     dateYear -= 1;
                 }
 
-                if (!resourceManager.getClimaticResource().getYears()
-                        .contains(dateYear)) {
+                if (!climaticResource.getYears().contains(dateYear)) {
                     continue;
                 }
 
@@ -368,7 +363,6 @@ public final class Evaluation extends CompositeIndicator {
                 annualPhase.setEndStage(endStageName);
                 annualPhase.setStartStage(startStageName);
                 annualPhase.setUid(phaseId);
-                computedPhases.add(annualPhase);
                 final PhaseResult phaseResult = new PhaseResult();
                 phaseResult.setAnnualPhase(annualPhase);
                 results.get(year).getPhaseResults().add(phaseResult);
@@ -399,15 +393,19 @@ public final class Evaluation extends CompositeIndicator {
                 }
 
                 /* Données climatiques pendant la phase et l'année donnée */
-                final ClimaticResource climaticData = resourceManager
-                        .getClimaticResource().getClimaticDataByPhaseAndYear(startDate, endDate);
+                final ClimaticResource climaticData = climaticResource
+                        .getClimaticDataByPhaseAndYear(startDate, endDate);
                 if (climaticData.isEmpty()) {
-                    if (resourceManager.getClimaticResource().isEmpty()) {
+                    if (climaticResource.isEmpty()) {
                         throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY);
                     }
+                    if (ignoreEmptyClimaticData) {
+                        continue;
+                    }
                     final int yearToSearch = dateYear;
-                    final List<ClimaticDailyData> ddataList = resourceManager.getClimaticResource().getData()
-                            .stream().filter(f -> f.getYear() == yearToSearch).collect(Collectors.toList());
+                    final List<ClimaticDailyData> ddataList = climaticResource.getData().stream() //
+                            .filter(f -> f.getYear() == yearToSearch) //
+                            .collect(Collectors.toList());
 
                     final ClimaticDailyData startData = ddataList.get(0);
                     final ClimaticDailyData endData = ddataList.get(ddataList.size() - 1);
@@ -433,9 +431,43 @@ public final class Evaluation extends CompositeIndicator {
             }
         }
 
-        computeFaisability();
-        fireIndicatorEvent(IndicatorEvent.Type.COMPUTE_SUCCESS.event(this));
+        computeFaisability(results);
         LOGGER.trace("end of computing evaluation \"{}\"", getName());
+        return results;
+    }
+
+    /**
+     * Compute indicator results for each step of provided climatic data.
+     *
+     * @return Results of computation by year, for each step in climatic data.
+     * @throws IndicatorsException
+     *             from Indicator.compute()
+     */
+    public Map<Date, Map<Integer, EvaluationResult>> computeEach() throws IndicatorsException {
+        LOGGER.info("Start");
+        this.ignoreEmptyClimaticData = true;
+        final List<CompositeIndicator> phases = getPhases();
+        final ClimaticResource climaticResource = resourceManager.getClimaticResource();
+        checkBeforeCompute(phases, climaticResource);
+
+        final List<ClimaticDailyData> dailyData = climaticResource.getData();
+        // first, create Maps
+        final Map<Date, Map<Integer, EvaluationResult>> allResults = new LinkedHashMap<>();
+        //dailyData.stream().map(DailyData::getDate).forEach(date -> allResults.put(date, new LinkedHashMap<>()));
+        // then compute
+        final ClimaticResource resource = new ClimaticResource();
+        resource.setMissingVariables(climaticResource.getMissingVariables());
+        for (int i = 0; i < dailyData.size(); i++) {
+            final Date date = dailyData.get(i).getDate();
+            LOGGER.info("Compute {}", date);
+            final List<ClimaticDailyData> data = dailyData.subList(0, i);
+            resource.setData(data);
+            var results = compute(resource, phases);
+            LOGGER.info("Results : {}", results);
+            allResults.put(date, results);
+        }
+        LOGGER.info("End");
+        return allResults;
     }
 
     /**
@@ -448,9 +480,10 @@ public final class Evaluation extends CompositeIndicator {
      * Pour chaque année, Pour chaque phase, valeurs.onIndicatorAdd(valeur phase
      * p, année n); Fin pour aggregation(valeurs); Fin pour;
      *
+     * @param results Results of computation by year.
      * @throws IndicatorsException raised by AggregationFunction.aggregate()
      */
-    private void computeFaisability() throws IndicatorsException {
+    private void computeFaisability(final Map<Integer, EvaluationResult> results) throws IndicatorsException {
         LOGGER.traceEntry();
         if (getType() == EvaluationType.WITHOUT_AGGREGATION) {
             return;
@@ -459,18 +492,14 @@ public final class Evaluation extends CompositeIndicator {
             // if only 1 phase, no need to aggregate
             // evaluation value = value of the phase
             results.values().forEach(result ->
-            result.setNormalizedValue(
-                    result.getPhaseResults().get(0).getNormalizedValue()
-                    )
-                    );
+                    result.setNormalizedValue(result.getPhaseResults().get(0).getNormalizedValue()));
             return;
         } else if (getAggregationFunction().getExpression() == null) {
             throw new IllegalStateException("An evaluation with more than 1 "
                     + "phase must have a defined expression for aggregation!");
         }
 
-        for (final Map.Entry<Integer, EvaluationResult> entry
-                : results.entrySet()) {
+        for (final Map.Entry<Integer, EvaluationResult> entry : results.entrySet()) {
             final EvaluationResult evaluationResult = entry.getValue();
             final int year = entry.getKey();
             if (evaluationResult == null) {
@@ -872,22 +901,6 @@ public final class Evaluation extends CompositeIndicator {
         return result;
     }
 
-    /**
-     * Context: Deserialization does not initialize results.
-     *
-     * A final field must be initialized either by direct assignment of an initial value or in the constructor. During
-     * deserialization, neither of these are invoked, so initial values for transients must be set in the
-     * 'readObject()' private method that's invoked during deserialization. And for that to work, the transients must
-     * be non-final.
-     *
-     * @param ois input stream from deserialization
-     */
-    private void readObject(final ObjectInputStream ois) throws ClassNotFoundException, IOException {
-        // perform the default de-serialization first
-        ois.defaultReadObject();
-        results = new HashMap<>();
-    }
-
     /**
      * Set parameters (id and attributes) for the indicator and its criteria
      * from knowledge defined in settings.
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
index c0af4e5e..e0b76d50 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
@@ -252,14 +252,14 @@ implements DataLoadingListener, Detailable, HasDataLoadingListener, Comparable<I
     }
 
     @Override
-    public final Double compute(final Resource<? extends DailyData> climRessource) throws IndicatorsException {
-        if (climRessource.getYears().isEmpty()) {
+    public final Double compute(final Resource<? extends DailyData> climResource) throws IndicatorsException {
+        if (climResource.getYears().isEmpty()) {
             throw new RuntimeException(
                     String.format(
                             "No years in ClimaticResource (%d dailyData)!",
-                            climRessource.getData().size()));
+                            climResource.getData().size()));
         }
-        final HashMap<String, Double> results = new HashMap<>();
+        final Map<String, Double> results = new HashMap<>();
         double valueAfterAggregation = 0;
         Double valueAfterNormalization;
 
@@ -274,7 +274,7 @@ implements DataLoadingListener, Detailable, HasDataLoadingListener, Comparable<I
             }
 
             try {
-                final double value = indicator.compute(climRessource);
+                final Double value = indicator.compute(climResource);
                 results.put(indicator.getId(), value);
             } catch (final IndicatorsException e) {
                 throw new IndicatorsException(ComputationErrorType.COMPOSITE_COMPUTATION, e, indicator.getId());
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Indicator.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Indicator.java
index 22969b6c..e383f79e 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Indicator.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Indicator.java
@@ -186,6 +186,7 @@ Serializable, UseVariables {
      * Normalized value.
      */
     @XmlTransient
+    @Getter
     @Setter
     private Double value;
 
@@ -225,8 +226,8 @@ Serializable, UseVariables {
             }
         }
         listeners.add(IndicatorListener.class, listener);
-        if (this instanceof CompositeIndicator) {
-            ((CompositeIndicator) this).getIndicators()
+        if (this instanceof CompositeIndicator compositeIndicator) {
+            compositeIndicator.getIndicators()
             .forEach(i -> i.addIndicatorListener(listener));
         }
     }
@@ -364,15 +365,6 @@ Serializable, UseVariables {
      */
     public abstract EvaluationType getType();
 
-    /**
-     * The normalized value.
-     *
-     * @return Normalized value
-     */
-    public final Double getValue() {
-        return value;
-    }
-
     /**
      * @param indicatorCategory indicator category
      */
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java
index f90f64f0..0de0bdb9 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java
@@ -2,8 +2,8 @@ package fr.inrae.agroclim.indicators.model;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.util.List;
@@ -52,16 +52,16 @@ public class EvaluationHourlyTest extends DataTestHelper {
     @Test
     public void compute() {
         assertTrue("Evaluation must not be null!", evaluation != null);
-        String error = null;
+        final Map<Integer, EvaluationResult> results;
         try {
             evaluation.initializeResources();
-            evaluation.compute();
+            results = evaluation.compute();
         } catch (final IndicatorsException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
 
-        final Map<Integer, EvaluationResult> results = evaluation.getResults();
         assertNotNull(results);
         assertEquals(1, results.keySet().size());
         assertTrue(results.containsKey(2015));
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java
index 8e99a6bc..38d60322 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java
@@ -20,8 +20,8 @@ package fr.inrae.agroclim.indicators.model;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.util.Arrays;
@@ -80,16 +80,16 @@ public class EvaluationRobertTest extends DataTestHelper {
     @Test
     public void compute() {
         assertTrue("Evaluation must not be null!", evaluation != null);
-        String error = null;
+        final Map<Integer, EvaluationResult> results;
         try {
             evaluation.initializeResources();
-            evaluation.compute();
+            results = evaluation.compute();
         } catch (final IndicatorsException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
 
-        final Map<Integer, EvaluationResult> results = evaluation.getResults();
         assertNotNull(results);
         assertEquals(2, results.keySet().size());
         assertTrue(results.containsKey(1991));
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
index a3f975d4..85bf316e 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -49,6 +50,7 @@ import fr.inrae.agroclim.indicators.model.indicator.Indicator;
 import fr.inrae.agroclim.indicators.model.result.EvaluationResult;
 import fr.inrae.agroclim.indicators.model.result.IndicatorResult;
 import fr.inrae.agroclim.indicators.model.result.PhaseResult;
+import java.util.Date;
 import lombok.NonNull;
 import lombok.extern.log4j.Log4j2;
 
@@ -128,6 +130,32 @@ public final class EvaluationTest extends DataTestHelper {
         assertNull(error, error);
     }
 
+    /**
+     * Check if evaluation computes.
+     */
+    @Test
+    public void computeEach() {
+        assertTrue("Evaluation must not be null!", evaluation != null);
+        final Map<Date, Map<Integer, EvaluationResult>> results;
+        try {
+            evaluation.initializeResources();
+            results = evaluation.computeEach();
+        } catch (final IndicatorsException e) {
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
+        }
+        assertNotNull(results);
+
+        assertEquals(evaluation.getClimaticResource().getData().size(), results.size());
+        final List<Integer> years = results.values().stream().flatMap(m -> m.keySet().stream()).distinct().toList();
+        assertNotNull(years);
+        System.out.println(years);
+        assertEquals(1, years.size());
+        final Integer expectedYear = 2015;
+        assertEquals(expectedYear, years.get(0));
+    }
+
     @Test
     public void computableWithNoData() {
     	final File xmlFile = getEvaluationWithNoDataTestFile();
@@ -184,8 +212,11 @@ public final class EvaluationTest extends DataTestHelper {
         String error = null;
         try {
             evaluation.initializeResources();
-            evaluation.compute();
-            final List<AnnualPhase> phases = evaluation.getComputedPhases();
+            final Map<Integer, EvaluationResult> results = evaluation.compute();
+            final List<AnnualPhase> phases = results.values().stream() //
+                    .flatMap(r -> r.getPhaseResults().stream()) //
+                    .map(r -> r.getAnnualPhase()) //
+                    .toList();
             final int nbOfPhases = evaluation.getIndicators().size();
             final int nbOfYears = evaluation.getClimaticResource().getYears().size();
             assertEquals(nbOfPhases * nbOfYears, phases.size());
@@ -238,8 +269,7 @@ public final class EvaluationTest extends DataTestHelper {
         final String fmt = "%s | %4d | %s-%s | %f | %f";
         try {
             evaluation.initializeResources();
-            evaluation.compute();
-            final Map<Integer, EvaluationResult> results = evaluation.getResults();
+            final Map<Integer, EvaluationResult> results = evaluation.compute();
             assertNotNull("Results must not be null!", results);
             results.entrySet().forEach((entryER) -> {
                 final Integer year = entryER.getKey();
@@ -420,15 +450,16 @@ public final class EvaluationTest extends DataTestHelper {
     public void setParameters() {
         assertTrue("Evaluation must not be null!", evaluation != null);
         String error = null;
+        Map<Integer, EvaluationResult> results;
         try {
             evaluation.initializeResources();
-            evaluation.compute();
+            results = evaluation.compute();
         } catch (final IndicatorsException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
 
-        final Map<Integer, EvaluationResult> results = evaluation.getResults();
         final Double initValue = getResultValue("s0s1", "heat", 2015, results);
         assertNotNull("value of climatic effect \"heat\" must not be null", initValue);
 
@@ -438,11 +469,11 @@ public final class EvaluationTest extends DataTestHelper {
         parameters.put("Theat", 25.);
         evaluation.setParametersValues(parameters);
         try {
-            evaluation.compute();
+            results = evaluation.compute();
         } catch (IndicatorsException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
 
         final Double newValue = getResultValue("s0s1", "heat", 2015, results);
         assertNotEquals("init = " + initValue + ", new = " + newValue, initValue, newValue);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java
index d226c65e..d02a249d 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java
@@ -20,8 +20,8 @@ package fr.inrae.agroclim.indicators.model;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.util.Map;
@@ -63,15 +63,15 @@ public class EvalutationCustomHeadersTest extends DataTestHelper {
     @Test
     public void compute() {
         assertTrue("Evaluation must not be null!", evaluation != null);
-        String error = null;
+        final Map<Integer, EvaluationResult> results;
         try {
             evaluation.initializeResources();
-            evaluation.compute();
+            results = evaluation.compute();
         } catch (final IndicatorsException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
-        final Map<Integer, EvaluationResult> results = evaluation.getResults();
         assertNotNull("Results must not be null!", results);
         assertEquals("One year en climate file", 1, results.keySet().size());
         results.values().stream().flatMap(v -> v.getPhaseResults().stream()).forEach(phaseResult -> {
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java
index 82613882..a102b23a 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java
@@ -17,7 +17,6 @@
 package fr.inrae.agroclim.indicators.model;
 
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
@@ -35,6 +34,7 @@ import fr.inrae.agroclim.indicators.model.result.EvaluationResult;
 import fr.inrae.agroclim.indicators.model.result.IndicatorResult;
 import fr.inrae.agroclim.indicators.model.result.PhaseResult;
 import lombok.extern.log4j.Log4j2;
+import static org.junit.Assert.fail;
 
 /**
  * Test that Marine Marjou's Getari file computes.
@@ -76,16 +76,17 @@ public class MMarjouTest extends DataTestHelper {
     @Test
     public void raidaysInfNbDaysInPhase() {
         assertTrue("Evaluation must not be null!", evaluation != null);
-        String error = null;
+        final Map<Integer, EvaluationResult> results;
         try {
             evaluation.initializeResources();
             long start = System.currentTimeMillis();
-            evaluation.compute();
+            results = evaluation.compute();
             LOGGER.info("Evaluation.compute() last {} seconds", (System.currentTimeMillis() - start) / 1000.);
         } catch (final IndicatorsException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
         // extract stages to compute number of days
         Map<Integer, Map<String, Integer>> stageDates = new HashMap<>();
         List<AnnualStageData> aSDatas;
@@ -100,9 +101,8 @@ public class MMarjouTest extends DataTestHelper {
             });
         });
         // check excraidays, hraidays and raidays
-        assertFalse(evaluation.getResults().isEmpty());
+        assertFalse(results.isEmpty());
         String fmt = "%d | %s %s : %d | %s=%.4f";
-        Map<Integer, EvaluationResult> results = evaluation.getResults();
         for (Map.Entry<Integer, EvaluationResult> entry : results.entrySet()) {
             Integer year = entry.getKey();
             if (!stageDates.containsKey(year)) {
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java
index c9c8ba33..c31942d7 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java
@@ -19,8 +19,8 @@ package fr.inrae.agroclim.indicators.model;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.io.IOException;
@@ -208,8 +208,8 @@ public class RaidayMeantTest extends DataTestHelper {
     @Test
     public void computeUsingPhenologyCalculator() {
         assertTrue("Evaluation must not be null!", evaluation != null);
-        String error = null;
-        List<AnnualStageData> annualStageDatas = null;
+        final List<AnnualStageData> annualStageDatas;
+        final Map<Integer, EvaluationResult> results;
         try {
             final EvaluationSettings settings = evaluation.getSettings();
             // wheat Soissons
@@ -221,25 +221,29 @@ public class RaidayMeantTest extends DataTestHelper {
             settings.getPhenologyLoader().setCalculator(calc);
             settings.getPhenologyLoader().setFile(null);
             evaluation.initializeResources();
-            evaluation.compute();
+            results = evaluation.compute();
             annualStageDatas = evaluation.getResourceManager().getPhenologicalResource().getData();
         } catch (final IndicatorsException | IOException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
 
         // first year in phenology file
         final int firstYear = 1959;
         assertNotNull(annualStageDatas);
         assertFalse(annualStageDatas.isEmpty());
         assertTrue(firstYear == annualStageDatas.get(0).getYear());
-        final List<AnnualPhase> computedPhases = evaluation.getComputedPhases();
+        final List<AnnualPhase> computedPhases = results.values().stream() //
+                .flatMap(r -> r.getPhaseResults().stream()) //
+                .map(r -> r.getAnnualPhase()) //
+                .toList();
         assertNotNull(computedPhases);
         assertFalse(computedPhases.isEmpty());
         assertTrue(firstYear == computedPhases.get(0).getHarvestYear());
         compareStagesAndPhases(annualStageDatas, computedPhases);
 
-        assertFalse(evaluation.getResults().isEmpty());
+        assertFalse(results.isEmpty());
     }
 
     /**
@@ -248,14 +252,15 @@ public class RaidayMeantTest extends DataTestHelper {
     @Test
     public void compute() {
         assertTrue("Evaluation must not be null!", evaluation != null);
-        String error = null;
+        final Map<Integer, EvaluationResult> results;
         try {
             evaluation.initializeResources();
-            evaluation.compute();
+            results = evaluation.compute();
         } catch (final IndicatorsException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
         // extract stages to compute number of days
         final Map<Integer, Map<String, Integer>> stageDates = new HashMap<>();
         List<AnnualStageData> aSDatas;
@@ -271,9 +276,9 @@ public class RaidayMeantTest extends DataTestHelper {
         });
 
         // check excraidays, hraidays and raidays
-        assertFalse(evaluation.getResults().isEmpty());
+        assertFalse(results.isEmpty());
         final String fmt = "%d | %s %s : %d | %s=%.4f";
-        for (final Map.Entry<Integer, EvaluationResult> entry : evaluation.getResults().entrySet()) {
+        for (final Map.Entry<Integer, EvaluationResult> entry : results.entrySet()) {
             final Integer year = entry.getKey();
             if (!stageDates.containsKey(year)) {
                 LOGGER.trace("No stage date for year {}", year);
@@ -317,14 +322,16 @@ public class RaidayMeantTest extends DataTestHelper {
         }
 
         assertNotNull(aSDatas);
-        final List<AnnualPhase> computedPhases = evaluation.getComputedPhases();
+        final List<AnnualPhase> computedPhases = results.values().stream() //
+                .flatMap(r -> r.getPhaseResults().stream()) //
+                .map(r -> r.getAnnualPhase()) //
+                .toList();
         compareStagesAndPhases(aSDatas, computedPhases);
 
-        Map<String, Map<Integer, Map<String, Double>>> computation;
-        computation = new HashMap<>();
+        final Map<String, Map<Integer, Map<String, Double>>> computation = new HashMap<>();
 
-        evaluation.getResults().forEach((year, results) -> {
-            results.getPhaseResults().forEach((phase) -> {
+        results.forEach((year, r) -> {
+            r.getPhaseResults().forEach((phase) -> {
                 final String phaseId = phase.getPhaseId();
                 if (!computation.containsKey(phaseId)) {
                     computation.put(phaseId, new HashMap<>());
-- 
GitLab


From 2185516a0f8c821fef9c79bf90923e68e710eb5e Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Mon, 19 Aug 2024 14:23:29 +0200
Subject: [PATCH 02/15] Ajouter des tests

---
 .../agroclim/indicators/model/Evaluation.java |  14 +-
 .../model/result/EvaluationResult.java        |   2 +
 .../model/EvaluationEachDateTest.java         | 134 ++++++++++++++++++
 .../indicators/model/EvaluationTest.java      |  27 ----
 .../indicators/xml/evaluation-phalen.gri      | 100 +++++++++++++
 5 files changed, 242 insertions(+), 35 deletions(-)
 create mode 100644 src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
 create mode 100644 src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri

diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
index 31baba23..8dceebc1 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
@@ -32,6 +32,7 @@ import java.util.stream.Collectors;
 
 import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType;
+import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.model.data.ResourceManager;
 import fr.inrae.agroclim.indicators.model.data.Variable;
 import fr.inrae.agroclim.indicators.model.data.Variable.Type;
@@ -439,12 +440,12 @@ public final class Evaluation extends CompositeIndicator {
     /**
      * Compute indicator results for each step of provided climatic data.
      *
-     * @return Results of computation by year, for each step in climatic data.
+     * @return Results of computation by year, for each step in climatic data:
+     * Climatic date ⮕ (year, {@link EvalutationResult}).
      * @throws IndicatorsException
      *             from Indicator.compute()
      */
-    public Map<Date, Map<Integer, EvaluationResult>> computeEach() throws IndicatorsException {
-        LOGGER.info("Start");
+    public Map<Date, Map<Integer, EvaluationResult>> computeEachDate() throws IndicatorsException {
         this.ignoreEmptyClimaticData = true;
         final List<CompositeIndicator> phases = getPhases();
         final ClimaticResource climaticResource = resourceManager.getClimaticResource();
@@ -453,20 +454,17 @@ public final class Evaluation extends CompositeIndicator {
         final List<ClimaticDailyData> dailyData = climaticResource.getData();
         // first, create Maps
         final Map<Date, Map<Integer, EvaluationResult>> allResults = new LinkedHashMap<>();
-        //dailyData.stream().map(DailyData::getDate).forEach(date -> allResults.put(date, new LinkedHashMap<>()));
+        dailyData.stream().map(DailyData::getDate).forEach(date -> allResults.put(date, new LinkedHashMap<>()));
         // then compute
         final ClimaticResource resource = new ClimaticResource();
         resource.setMissingVariables(climaticResource.getMissingVariables());
         for (int i = 0; i < dailyData.size(); i++) {
             final Date date = dailyData.get(i).getDate();
-            LOGGER.info("Compute {}", date);
             final List<ClimaticDailyData> data = dailyData.subList(0, i);
             resource.setData(data);
-            var results = compute(resource, phases);
-            LOGGER.info("Results : {}", results);
+            final Map<Integer, EvaluationResult> results = compute(resource, phases);
             allResults.put(date, results);
         }
-        LOGGER.info("End");
         return allResults;
     }
 
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/result/EvaluationResult.java b/src/main/java/fr/inrae/agroclim/indicators/model/result/EvaluationResult.java
index 70782bf1..d0fba97c 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/result/EvaluationResult.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/result/EvaluationResult.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import lombok.Getter;
+import lombok.ToString;
 
 /**
  * Normalized value of an evaluation for a year and values of composed phases.
@@ -30,6 +31,7 @@ import lombok.Getter;
  * @author $Author$
  * @version $Revision$
  */
+@ToString
 public class EvaluationResult extends Result {
 
     /**
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
new file mode 100644
index 00000000..5b8c6daa
--- /dev/null
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
@@ -0,0 +1,134 @@
+package fr.inrae.agroclim.indicators.model;
+
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
+import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
+import fr.inrae.agroclim.indicators.model.indicator.Indicator;
+import fr.inrae.agroclim.indicators.model.result.EvaluationResult;
+import fr.inrae.agroclim.indicators.model.result.IndicatorResult;
+import fr.inrae.agroclim.indicators.model.result.PhaseResult;
+import fr.inrae.agroclim.indicators.util.DateUtils;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test {@link Evaluation#computeEachDate()}.
+ *
+ * @author Olivier Maury
+ */
+public class EvaluationEachDateTest extends DataTestHelper {
+
+    /**
+     * Evaluation from good XML file.
+     */
+    private Evaluation evaluation;
+
+    /**
+     * Set up Evaluation to test.
+     */
+    @Before
+    public void beforeTest() {
+        final File xmlFile = getFile("xml/evaluation-phalen.gri");
+        evaluation = getEvaluation(xmlFile);
+    }
+
+    /**
+     * Check if evaluation computes.
+     */
+    @Test
+    public void computeEachDate() {
+        assertTrue("Evaluation must not be null!", evaluation != null);
+        final Map<Date, Map<Integer, EvaluationResult>> results;
+        try {
+            evaluation.initializeResources();
+            results = evaluation.computeEachDate();
+        } catch (final IndicatorsException e) {
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
+        }
+        assertNotNull(results);
+
+        assertEquals(evaluation.getClimaticResource().getData().size(), results.size());
+        final List<Integer> years = results.values().stream().flatMap(m -> m.keySet().stream()).distinct().toList();
+        assertNotNull(years);
+        assertEquals(1, years.size());
+        final Integer expectedYear = 2015;
+        assertEquals(expectedYear, years.get(0));
+
+        // s0s1 : phalen
+        final List<Indicator> indicators = new ArrayList<>();
+        final List<Indicator> allIndicators = new ArrayList<>();
+        allIndicators.addAll(evaluation.getIndicators());
+        Indicator indicator = allIndicators.remove(0);
+        while (indicator != null) {
+            if (indicator instanceof CompositeIndicator c) {
+                allIndicators.addAll(c.getIndicators());
+            } else {
+                indicators.add(indicator);
+            }
+            if (!allIndicators.isEmpty()) {
+                indicator = allIndicators.remove(0);
+            } else {
+                indicator = null;
+            }
+        }
+        final int nbOfIndicators = indicators.size();
+        assertEquals(1, nbOfIndicators);
+
+        // no data on 1st january 2015 as no phase
+        final Optional<Date> firstDateOptional = results.keySet().stream().findFirst();
+        assertTrue(firstDateOptional.isPresent());
+        final Date firstDate = firstDateOptional.get();
+        final Date expectedFirstDate = DateUtils.getDate(expectedYear, 1);
+        assertEquals(expectedFirstDate, firstDate);
+        assertFalse(results.get(firstDate).containsKey(expectedYear));
+
+        // s0s1 : 120 − 140
+        final int s0 = 120;
+        final int s0Plus1 = s0 + 1;
+        final int s1 = 140;
+        List.of(s0Plus1, s1).forEach(doy -> {
+            final Date date = DateUtils.getDate(expectedYear, doy);
+            final List<PhaseResult> phaseResults = results.get(date).get(expectedYear).getPhaseResults();
+            assertFalse(phaseResults.isEmpty());
+            assertEquals(1, phaseResults.size());
+            final List<IndicatorResult> processResults = phaseResults.get(0).getIndicatorResults();
+            assertNotNull("On " + date + "/" + doy +", process results must not be null!", processResults);
+            assertEquals(1, processResults.size());
+            assertEquals("growth", processResults.get(0).getIndicatorId());
+
+            // practices > growth > phalength > phalen
+            final List<IndicatorResult> practicesResults = processResults.get(0).getIndicatorResults();
+            assertEquals("phalength", practicesResults.get(0).getIndicatorId());
+            List<IndicatorResult> indicatorResults = practicesResults.get(0).getIndicatorResults();
+            assertEquals("phalen", indicatorResults.get(0).getIndicatorId());
+            Double value = indicatorResults.get(0).getRawValue();
+            assertNotNull("On " + date + "/" + doy + ", phalen must not be null!", value);
+        });
+
+        // Check values
+        for (int doy = s0Plus1; doy < s1; doy++) {
+            final Date date = DateUtils.getDate(expectedYear, doy);
+            final List<PhaseResult> phaseResults = results.get(date).get(expectedYear).getPhaseResults();
+            final List<IndicatorResult> processResults = phaseResults.get(0).getIndicatorResults();
+            final List<IndicatorResult> practicesResults = processResults.get(0).getIndicatorResults();
+            final List<IndicatorResult> indicatorResults = practicesResults.get(0).getIndicatorResults();
+            final Double value = indicatorResults.get(0).getRawValue();
+            assertNotNull("On " + date + "/" + doy + ", phalen must not be null!", value);
+            final Double expected = Double.valueOf(doy - s0);
+            assertEquals("On " + date + "/" + doy + ", phalen must equal " + expected, expected, value);
+        }
+    }
+}
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
index 85bf316e..0603a053 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
@@ -50,7 +50,6 @@ import fr.inrae.agroclim.indicators.model.indicator.Indicator;
 import fr.inrae.agroclim.indicators.model.result.EvaluationResult;
 import fr.inrae.agroclim.indicators.model.result.IndicatorResult;
 import fr.inrae.agroclim.indicators.model.result.PhaseResult;
-import java.util.Date;
 import lombok.NonNull;
 import lombok.extern.log4j.Log4j2;
 
@@ -130,32 +129,6 @@ public final class EvaluationTest extends DataTestHelper {
         assertNull(error, error);
     }
 
-    /**
-     * Check if evaluation computes.
-     */
-    @Test
-    public void computeEach() {
-        assertTrue("Evaluation must not be null!", evaluation != null);
-        final Map<Date, Map<Integer, EvaluationResult>> results;
-        try {
-            evaluation.initializeResources();
-            results = evaluation.computeEach();
-        } catch (final IndicatorsException e) {
-            final String error = e.getClass() + " : " + e.getLocalizedMessage();
-            fail(error);
-            return;
-        }
-        assertNotNull(results);
-
-        assertEquals(evaluation.getClimaticResource().getData().size(), results.size());
-        final List<Integer> years = results.values().stream().flatMap(m -> m.keySet().stream()).distinct().toList();
-        assertNotNull(years);
-        System.out.println(years);
-        assertEquals(1, years.size());
-        final Integer expectedYear = 2015;
-        assertEquals(expectedYear, years.get(0));
-    }
-
     @Test
     public void computableWithNoData() {
     	final File xmlFile = getEvaluationWithNoDataTestFile();
diff --git a/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri b/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri
new file mode 100644
index 00000000..5d756803
--- /dev/null
+++ b/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!DOCTYPE evaluationSettings PUBLIC
+    "-//INRAE AgroClim.//DTD Evaluation 1.1//EN"
+    "https://agroclim.inrae.fr/getari/dtd/1.1/evaluation.dtd">
+<evaluationSettings timescale="DAILY" timestamp="2024-08-19T13:43:21.127972376" version="1.2.2+20230809135003">
+    <climate>
+        <file path="../model/data/climate/climate-2015.csv">
+            <separator>	</separator>
+            <header></header>
+            <header>year</header>
+            <header>month</header>
+            <header>day</header>
+            <header>tmin</header>
+            <header>tmax</header>
+            <header>tmean</header>
+            <header></header>
+            <header>radiation</header>
+            <header>rain</header>
+            <header>rh</header>
+            <header>wind</header>
+            <header>ETP</header>
+            <midnight>0</midnight>
+        </file>
+    </climate>
+    <notes>
+        <note>
+            <id>10.1016/j.eja.2019.125960</id>
+            <description>Future development of apricot blossom blight under climate change in Southern France</description>
+        </note>
+        <note>
+            <id>978-2-7148-0083-1</id>
+            <description>La ventilation des bâtiments d'élevage de ruminants, Institut du Végétal</description>
+        </note>
+    </notes>
+    <phenology>
+        <file path="../model/data/phenology/pheno_sample.csv">
+            <separator>;</separator>
+            <header>year</header>
+            <header>s0</header>
+            <header>s1</header>
+            <header>s2</header>
+            <header>s3</header>
+            <header>s4</header>
+        </file>
+    </phenology>
+    <name>evaluation-test</name>
+    <type>WITH_AGGREGATION</type>
+    <compositeIndicator>
+        <name>root-test</name>
+        <name xml:lang="en">evaluation-test</name>
+        <id>root-evaluation</id>
+        <timescale>DAILY</timescale>
+        <tag>practices</tag>
+        <indicator xsi:type="compositeIndicator" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+            <name>s1</name>
+            <id>s0s1</id>
+            <category>pheno</category>
+            <color>#5F9EA0</color>
+            <timescale>DAILY</timescale>
+            <tag>pheno-s0-s1</tag>
+            <indicator xsi:type="compositeIndicator">
+                <name>s0</name>
+                <id>pheno_s0</id>
+                <category>pheno</category>
+                <color>#5F9EA0</color>
+                <timescale>DAILY</timescale>
+                <normalizationFunction xsi:type="exponential" expA="0.0" expB="0.0"/>
+                <tag>pheno-s0-s1</tag>
+            </indicator>
+            <indicator xsi:type="compositeIndicator">
+                <name xml:lang="en">Crop growth</name>
+                <name xml:lang="fr">Développement cultural</name>
+                <id>growth</id>
+                <category>ecoprocesses</category>
+                <color>#5F9EA0</color>
+                <timescale>DAILY</timescale>
+                <indicator xsi:type="compositeIndicator">
+                    <name xml:lang="fr">Durée de la phase</name>
+                    <name xml:lang="en">Phase length</name>
+                    <id>phalength</id>
+                    <category>climatic</category>
+                    <color>#5F9EA0</color>
+                    <timescale>DAILY</timescale>
+                    <indicator xsi:type="phaseLength">
+                        <description xml:lang="fr">Durée de la phase.</description>
+                        <description xml:lang="en">Phase length.</description>
+                        <name xml:lang="fr">Durée de la phase</name>
+                        <name xml:lang="en">Phase length</name>
+                        <id>phalen</id>
+                        <category>indicator</category>
+                        <color>#5F9EA0</color>
+                        <timescale>DAILY</timescale>
+                        <normalizationFunction xsi:type="sigmoid" sigmoidA="30.0" sigmoidB="4.0"/>
+                        <unit>day</unit>
+                    </indicator>
+                </indicator>
+            </indicator>
+        </indicator>
+    </compositeIndicator>
+</evaluationSettings>
-- 
GitLab


From 353025816a393c4ae15ec1b6f8e6a4dea60cbaea Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Mon, 19 Aug 2024 14:40:47 +0200
Subject: [PATCH 03/15] =?UTF-8?q?D=C3=A9couper=20le=20test=20en=20plusieur?=
 =?UTF-8?q?s=20m=C3=A9thodes?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../model/EvaluationEachDateTest.java         | 105 ++++++++++++------
 .../indicators/xml/evaluation-phalen.gri      |  16 +--
 2 files changed, 78 insertions(+), 43 deletions(-)

diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
index 5b8c6daa..61ded8b0 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
@@ -19,7 +19,7 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 /**
@@ -29,44 +29,74 @@ import org.junit.Test;
  */
 public class EvaluationEachDateTest extends DataTestHelper {
 
+    /**
+     * The only year in climatic data.
+     */
+    private static final Integer YEAR = 2015;
+
     /**
      * Evaluation from good XML file.
      */
-    private Evaluation evaluation;
+    private static Evaluation evaluation;
 
     /**
-     * Set up Evaluation to test.
+     * Computation results.
      */
-    @Before
-    public void beforeTest() {
-        final File xmlFile = getFile("xml/evaluation-phalen.gri");
-        evaluation = getEvaluation(xmlFile);
-    }
+    private static Map<Date, Map<Integer, EvaluationResult>> results;
+
+    /**
+     * DOY of Stage 0.
+     */
+    private static final int S0 = 120;
+
+    /**
+     * DOY of Stage 0 plus 1 day.
+     */
+    private static final int S0_PLUS_1 = S0 + 1;
+
+    /**
+     * DOY of Stage 1.
+     */
+    private static final int S1 = 140;
 
     /**
+     * Set up Evaluation to test.
+     *
      * Check if evaluation computes.
      */
-    @Test
-    public void computeEachDate() {
+    @BeforeClass
+    public static void beforeTest() {
+        final File xmlFile = getFile("xml/evaluation-phalen.gri");
+        evaluation = getEvaluation(xmlFile);
         assertTrue("Evaluation must not be null!", evaluation != null);
-        final Map<Date, Map<Integer, EvaluationResult>> results;
         try {
             evaluation.initializeResources();
             results = evaluation.computeEachDate();
         } catch (final IndicatorsException e) {
             final String error = e.getClass() + " : " + e.getLocalizedMessage();
             fail(error);
-            return;
         }
+    }
+
+    /**
+     * Ensure only 2015 has data.
+     */
+    @Test
+    public void years() {
         assertNotNull(results);
 
         assertEquals(evaluation.getClimaticResource().getData().size(), results.size());
         final List<Integer> years = results.values().stream().flatMap(m -> m.keySet().stream()).distinct().toList();
         assertNotNull(years);
         assertEquals(1, years.size());
-        final Integer expectedYear = 2015;
-        assertEquals(expectedYear, years.get(0));
+        assertEquals(YEAR, years.get(0));
+    }
 
+    /**
+     * Ensure only 1 indicator is computed.
+     */
+    @Test
+    public void indicators() {
         // s0s1 : phalen
         final List<Indicator> indicators = new ArrayList<>();
         final List<Indicator> allIndicators = new ArrayList<>();
@@ -86,26 +116,34 @@ public class EvaluationEachDateTest extends DataTestHelper {
         }
         final int nbOfIndicators = indicators.size();
         assertEquals(1, nbOfIndicators);
+    }
 
+    /**
+     * Ensure no data is returned out of s0s1.
+     */
+    @Test
+    public void noDataOutOfPhase() {
         // no data on 1st january 2015 as no phase
         final Optional<Date> firstDateOptional = results.keySet().stream().findFirst();
         assertTrue(firstDateOptional.isPresent());
         final Date firstDate = firstDateOptional.get();
-        final Date expectedFirstDate = DateUtils.getDate(expectedYear, 1);
+        final Date expectedFirstDate = DateUtils.getDate(YEAR, 1);
         assertEquals(expectedFirstDate, firstDate);
-        assertFalse(results.get(firstDate).containsKey(expectedYear));
-
-        // s0s1 : 120 − 140
-        final int s0 = 120;
-        final int s0Plus1 = s0 + 1;
-        final int s1 = 140;
-        List.of(s0Plus1, s1).forEach(doy -> {
-            final Date date = DateUtils.getDate(expectedYear, doy);
-            final List<PhaseResult> phaseResults = results.get(date).get(expectedYear).getPhaseResults();
+        assertFalse(results.get(firstDate).containsKey(YEAR));
+    }
+
+    /**
+     * Ensure data are return each date of s0s1.
+     */
+    @Test
+    public void dataInPhase() {
+        for (int doy = S0_PLUS_1; doy < S1; doy++) {
+            final Date date = DateUtils.getDate(YEAR, doy);
+            final List<PhaseResult> phaseResults = results.get(date).get(YEAR).getPhaseResults();
             assertFalse(phaseResults.isEmpty());
             assertEquals(1, phaseResults.size());
             final List<IndicatorResult> processResults = phaseResults.get(0).getIndicatorResults();
-            assertNotNull("On " + date + "/" + doy +", process results must not be null!", processResults);
+            assertNotNull("On " + date + "/" + doy + ", process results must not be null!", processResults);
             assertEquals(1, processResults.size());
             assertEquals("growth", processResults.get(0).getIndicatorId());
 
@@ -116,18 +154,23 @@ public class EvaluationEachDateTest extends DataTestHelper {
             assertEquals("phalen", indicatorResults.get(0).getIndicatorId());
             Double value = indicatorResults.get(0).getRawValue();
             assertNotNull("On " + date + "/" + doy + ", phalen must not be null!", value);
-        });
+        }
+    }
 
-        // Check values
-        for (int doy = s0Plus1; doy < s1; doy++) {
-            final Date date = DateUtils.getDate(expectedYear, doy);
-            final List<PhaseResult> phaseResults = results.get(date).get(expectedYear).getPhaseResults();
+    /**
+     * Check computed values for each date.
+     */
+    @Test
+    public void checkData() {
+        for (int doy = S0_PLUS_1; doy < S1; doy++) {
+            final Date date = DateUtils.getDate(YEAR, doy);
+            final List<PhaseResult> phaseResults = results.get(date).get(YEAR).getPhaseResults();
             final List<IndicatorResult> processResults = phaseResults.get(0).getIndicatorResults();
             final List<IndicatorResult> practicesResults = processResults.get(0).getIndicatorResults();
             final List<IndicatorResult> indicatorResults = practicesResults.get(0).getIndicatorResults();
             final Double value = indicatorResults.get(0).getRawValue();
             assertNotNull("On " + date + "/" + doy + ", phalen must not be null!", value);
-            final Double expected = Double.valueOf(doy - s0);
+            final Double expected = Double.valueOf(doy - S0);
             assertEquals("On " + date + "/" + doy + ", phalen must equal " + expected, expected, value);
         }
     }
diff --git a/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri b/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri
index 5d756803..b11858c3 100644
--- a/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri
+++ b/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri
@@ -2,7 +2,7 @@
 <!DOCTYPE evaluationSettings PUBLIC
     "-//INRAE AgroClim.//DTD Evaluation 1.1//EN"
     "https://agroclim.inrae.fr/getari/dtd/1.1/evaluation.dtd">
-<evaluationSettings timescale="DAILY" timestamp="2024-08-19T13:43:21.127972376" version="1.2.2+20230809135003">
+<evaluationSettings timescale="DAILY" timestamp="2024-08-19T14:26:01.505524546" version="2.0.1+20240405062628">
     <climate>
         <file path="../model/data/climate/climate-2015.csv">
             <separator>	</separator>
@@ -22,16 +22,7 @@
             <midnight>0</midnight>
         </file>
     </climate>
-    <notes>
-        <note>
-            <id>10.1016/j.eja.2019.125960</id>
-            <description>Future development of apricot blossom blight under climate change in Southern France</description>
-        </note>
-        <note>
-            <id>978-2-7148-0083-1</id>
-            <description>La ventilation des bâtiments d'élevage de ruminants, Institut du Végétal</description>
-        </note>
-    </notes>
+    <notes/>
     <phenology>
         <file path="../model/data/phenology/pheno_sample.csv">
             <separator>;</separator>
@@ -43,7 +34,7 @@
             <header>s4</header>
         </file>
     </phenology>
-    <name>evaluation-test</name>
+    <name>evaluation-phalen</name>
     <type>WITH_AGGREGATION</type>
     <compositeIndicator>
         <name>root-test</name>
@@ -51,6 +42,7 @@
         <id>root-evaluation</id>
         <timescale>DAILY</timescale>
         <tag>practices</tag>
+        <aggregationFunction xsi:type="jexlFunction" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
         <indicator xsi:type="compositeIndicator" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
             <name>s1</name>
             <id>s0s1</id>
-- 
GitLab


From 53b4ad231a3c08cfacbc2e33b4b52ee3b947deeb Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Mon, 19 Aug 2024 15:00:23 +0200
Subject: [PATCH 04/15] =?UTF-8?q?feat:=20:boom:=20Ajouter=20une=20m=C3=A9t?=
 =?UTF-8?q?hode=20pour=20calculer=20toutes=20les=20valeurs=20correspondant?=
 =?UTF-8?q?=20au=20jeu=20climatique=20fourni.=20refs=20agroclim/agrometinf?=
 =?UTF-8?q?o/AgroMetInfo=5F2.0#35?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

refs agroclim/agrometinfo/AgroMetInfo_2.0#35 Renvoyer les résultats de calcul par la méthode `Evaluation.compute()` et ne plus les stocker dans `Evaluation`.
---
 pom.xml                               |  2 +-
 src/site/markdown/release-notes-fr.md | 28 +++++++++++++++++++++++++++
 src/site/site.xml                     |  1 +
 3 files changed, 30 insertions(+), 1 deletion(-)
 create mode 100644 src/site/markdown/release-notes-fr.md

diff --git a/pom.xml b/pom.xml
index d5dfc484..22fcaede 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@ along with Indicators. If not, see <https://www.gnu.org/licenses/>.
     <name>Indicators</name>
     <description>Library of agro- and eco-climatic indicators.</description>
     <inceptionYear>2018</inceptionYear>
-    <version>2.0.3-SNAPSHOT</version>
+    <version>2.1.0-SNAPSHOT</version>
     <packaging>jar</packaging>
     <licenses>
         <license>
diff --git a/src/site/markdown/release-notes-fr.md b/src/site/markdown/release-notes-fr.md
new file mode 100644
index 00000000..e01a28e8
--- /dev/null
+++ b/src/site/markdown/release-notes-fr.md
@@ -0,0 +1,28 @@
+---
+title: Notes de version
+description: Modifications de la bibliothèque d'indicateurs.
+keywords: "version"
+date: 2024-08-19
+---
+
+# [v2.1.0](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.1.0) −
+
+- Ajouter la méthode `Evaluation.computeEachDate()`.
+- Renvoyer les résultats de calcul par la méthode `Evaluation.compute()` et ne plus les stocker dans `Evaluation`.
+
+# [v2.0.2](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.2) − 11 juillet 2024
+
+- Passer à Jakarta XML Binding.
+
+# [v2.0.1](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.1) − 5 avril 2024
+
+- Ajouter le modèle phénologique Richardson.
+- Renommer `CompositeIndicator.toAggregate(boolean)`.
+- Corriger l'id et la référence d'indicateurs THI.
+
+# [v2.0.0](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.0) − 18 janvier 2024
+
+- Gestion des données climatiques manquantes.
+- Changement de gestion des exceptions et affichage de codes d'erreurs.
+- Passage sous GitLab.
+- Mise en ligne de la documentation.
\ No newline at end of file
diff --git a/src/site/site.xml b/src/site/site.xml
index 0f490a99..89ea402e 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -30,6 +30,7 @@
             <item name="Indicateurs journaliers" href="indicators-daily-fr.html" />
             <item name="Codes d'erreurs" href="errors-fr.html" />
             <item name="Modèles phénologiques" href="pheno-fr.html" />
+            <item name="Notes de version" href="release-notes-fr.html" />
             <item name="Documentation in English" href="en/index.html" />
         </menu>
         <menu ref="modules" inherit="top" />
-- 
GitLab


From 8591dc21874836138ac449de644884256920008e Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Mon, 19 Aug 2024 15:49:06 +0200
Subject: [PATCH 05/15] Suppression de

---
 .../model/indicator/CompositeIndicator.java          | 12 ------------
 src/site/markdown/release-notes-fr.md                |  4 +++-
 2 files changed, 3 insertions(+), 13 deletions(-)

diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
index e0b76d50..b639fc70 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
@@ -651,18 +651,6 @@ implements DataLoadingListener, Detailable, HasDataLoadingListener, Comparable<I
         getIndicators().forEach(i -> i.setParametersValues(values));
     }
 
-    /**
-     * Detect if aggregation function is needed but missing.
-     *
-     * @param fire fire events while checking
-     * @return true if aggregation function is needed but missing
-     * @deprecated use {@link CompositeIndicator#isAggregationMissing(boolean)}.
-     */
-    @Deprecated(since = "2.0.1", forRemoval = true)
-    public final boolean toAggregate(final boolean fire) {
-        return isAggregationMissing(fire);
-    }
-
     @Override
     public final String toStringTree(final String indent) {
         final StringBuilder sb = new StringBuilder();
diff --git a/src/site/markdown/release-notes-fr.md b/src/site/markdown/release-notes-fr.md
index e01a28e8..9a462c8a 100644
--- a/src/site/markdown/release-notes-fr.md
+++ b/src/site/markdown/release-notes-fr.md
@@ -9,6 +9,7 @@ date: 2024-08-19
 
 - Ajouter la méthode `Evaluation.computeEachDate()`.
 - Renvoyer les résultats de calcul par la méthode `Evaluation.compute()` et ne plus les stocker dans `Evaluation`.
+- Suppression de `CompositeIndicator#toAggregate(boolean)`.
 
 # [v2.0.2](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.2) − 11 juillet 2024
 
@@ -19,10 +20,11 @@ date: 2024-08-19
 - Ajouter le modèle phénologique Richardson.
 - Renommer `CompositeIndicator.toAggregate(boolean)`.
 - Corriger l'id et la référence d'indicateurs THI.
+- Dépréciation `CompositeIndicator#toAggregate(boolean)`.
 
 # [v2.0.0](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.0) − 18 janvier 2024
 
 - Gestion des données climatiques manquantes.
 - Changement de gestion des exceptions et affichage de codes d'erreurs.
 - Passage sous GitLab.
-- Mise en ligne de la documentation.
\ No newline at end of file
+- Mise en ligne de la documentation.
-- 
GitLab


From 61635723c18adb04b2c539322a414e5dcca9ce25 Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Tue, 20 Aug 2024 09:31:27 +0200
Subject: [PATCH 06/15] Utiliser LocalDate

---
 .../agroclim/indicators/model/Evaluation.java     |  8 ++++----
 .../indicators/model/data/HourlyData.java         | 10 ++++++++++
 .../indicators/model/EvaluationEachDateTest.java  | 15 ++++++++-------
 3 files changed, 22 insertions(+), 11 deletions(-)

diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
index 8dceebc1..e228def7 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
@@ -445,7 +445,7 @@ public final class Evaluation extends CompositeIndicator {
      * @throws IndicatorsException
      *             from Indicator.compute()
      */
-    public Map<Date, Map<Integer, EvaluationResult>> computeEachDate() throws IndicatorsException {
+    public Map<LocalDate, Map<Integer, EvaluationResult>> computeEachDate() throws IndicatorsException {
         this.ignoreEmptyClimaticData = true;
         final List<CompositeIndicator> phases = getPhases();
         final ClimaticResource climaticResource = resourceManager.getClimaticResource();
@@ -453,16 +453,16 @@ public final class Evaluation extends CompositeIndicator {
 
         final List<ClimaticDailyData> dailyData = climaticResource.getData();
         // first, create Maps
-        final Map<Date, Map<Integer, EvaluationResult>> allResults = new LinkedHashMap<>();
-        dailyData.stream().map(DailyData::getDate).forEach(date -> allResults.put(date, new LinkedHashMap<>()));
+        final Map<LocalDate, Map<Integer, EvaluationResult>> allResults = new LinkedHashMap<>();
+        dailyData.stream().map(DailyData::getLocalDate).forEach(date -> allResults.put(date, new LinkedHashMap<>()));
         // then compute
         final ClimaticResource resource = new ClimaticResource();
         resource.setMissingVariables(climaticResource.getMissingVariables());
         for (int i = 0; i < dailyData.size(); i++) {
-            final Date date = dailyData.get(i).getDate();
             final List<ClimaticDailyData> data = dailyData.subList(0, i);
             resource.setData(data);
             final Map<Integer, EvaluationResult> results = compute(resource, phases);
+            final LocalDate date = dailyData.get(i).getLocalDate();
             allResults.put(date, results);
         }
         return allResults;
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/data/HourlyData.java b/src/main/java/fr/inrae/agroclim/indicators/model/data/HourlyData.java
index 5c6f5c28..97ecc147 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/data/HourlyData.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/data/HourlyData.java
@@ -21,6 +21,7 @@ import java.util.Calendar;
 import java.util.Date;
 
 import fr.inrae.agroclim.indicators.util.DateUtils;
+import java.time.LocalDate;
 import lombok.Getter;
 
 /**
@@ -174,6 +175,15 @@ public abstract class HourlyData implements Cloneable, Data, Serializable {
         return DateUtils.getDoy(getDate());
     }
 
+    /**
+     * Create date object with {@code day}, {@code month}, {@code year} and {@code hour} attribute.<br>
+     * If at least one of these attributes is null, then the returned date is null.
+     * @return date object
+     */
+    public LocalDate getLocalDate() {
+        return DateUtils.asLocalDate(getDate());
+    }
+
     /**
      * Get value for variable as it was set.
      *
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
index 61ded8b0..95dfba5c 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
@@ -9,8 +9,9 @@ import fr.inrae.agroclim.indicators.model.result.IndicatorResult;
 import fr.inrae.agroclim.indicators.model.result.PhaseResult;
 import fr.inrae.agroclim.indicators.util.DateUtils;
 import java.io.File;
+import java.time.LocalDate;
+import java.time.Month;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -42,7 +43,7 @@ public class EvaluationEachDateTest extends DataTestHelper {
     /**
      * Computation results.
      */
-    private static Map<Date, Map<Integer, EvaluationResult>> results;
+    private static Map<LocalDate, Map<Integer, EvaluationResult>> results;
 
     /**
      * DOY of Stage 0.
@@ -124,10 +125,10 @@ public class EvaluationEachDateTest extends DataTestHelper {
     @Test
     public void noDataOutOfPhase() {
         // no data on 1st january 2015 as no phase
-        final Optional<Date> firstDateOptional = results.keySet().stream().findFirst();
+        final Optional<LocalDate> firstDateOptional = results.keySet().stream().findFirst();
         assertTrue(firstDateOptional.isPresent());
-        final Date firstDate = firstDateOptional.get();
-        final Date expectedFirstDate = DateUtils.getDate(YEAR, 1);
+        final LocalDate firstDate = firstDateOptional.get();
+        final LocalDate expectedFirstDate = LocalDate.of(YEAR, Month.JANUARY, 1);
         assertEquals(expectedFirstDate, firstDate);
         assertFalse(results.get(firstDate).containsKey(YEAR));
     }
@@ -138,7 +139,7 @@ public class EvaluationEachDateTest extends DataTestHelper {
     @Test
     public void dataInPhase() {
         for (int doy = S0_PLUS_1; doy < S1; doy++) {
-            final Date date = DateUtils.getDate(YEAR, doy);
+            final LocalDate date = DateUtils.asLocalDate(DateUtils.getDate(YEAR, doy));
             final List<PhaseResult> phaseResults = results.get(date).get(YEAR).getPhaseResults();
             assertFalse(phaseResults.isEmpty());
             assertEquals(1, phaseResults.size());
@@ -163,7 +164,7 @@ public class EvaluationEachDateTest extends DataTestHelper {
     @Test
     public void checkData() {
         for (int doy = S0_PLUS_1; doy < S1; doy++) {
-            final Date date = DateUtils.getDate(YEAR, doy);
+            final LocalDate date = DateUtils.asLocalDate(DateUtils.getDate(YEAR, doy));
             final List<PhaseResult> phaseResults = results.get(date).get(YEAR).getPhaseResults();
             final List<IndicatorResult> processResults = phaseResults.get(0).getIndicatorResults();
             final List<IndicatorResult> practicesResults = processResults.get(0).getIndicatorResults();
-- 
GitLab


From db45e30371f2fa43347286e7763503e28fe39e0a Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Wed, 14 Aug 2024 17:02:23 +0200
Subject: [PATCH 07/15] =?UTF-8?q?Ajouter=20une=20m=C3=A9thode=20pour=20cal?=
 =?UTF-8?q?culer=20toutes=20les=20valeurs=20correspondant=20au=20jeu=20cli?=
 =?UTF-8?q?matique=20fourni.=20refs=20agroclim/agrometinfo/AgroMetInfo=5F2?=
 =?UTF-8?q?.0#35?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../agroclim/indicators/model/Evaluation.java | 163 ++++++++++--------
 .../model/indicator/CompositeIndicator.java   |  10 +-
 .../indicators/model/indicator/Indicator.java |  14 +-
 .../model/EvaluationHourlyTest.java           |  12 +-
 .../model/EvaluationRobertTest.java           |  12 +-
 .../indicators/model/EvaluationTest.java      |  51 ++++--
 .../model/EvalutationCustomHeadersTest.java   |  12 +-
 .../indicators/model/MMarjouTest.java         |  14 +-
 .../indicators/model/RaidayMeantTest.java     |  45 +++--
 9 files changed, 188 insertions(+), 145 deletions(-)

diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
index 8e78e9e4..31baba23 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
@@ -16,8 +16,6 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
-import java.io.IOException;
-import java.io.ObjectInputStream;
 import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -106,21 +104,6 @@ public final class Evaluation extends CompositeIndicator {
         });
     }
 
-    /**
-     * Computed phases (or phases from file) used to compute indicators.
-     *
-     * TODO : supprimer et utiliser
-     * EvaluationResult::getPhaseResults::getAnnualPhase.
-     */
-    @Getter
-    private transient List<AnnualPhase> computedPhases;
-
-    /**
-     * Results of computation by year.
-     */
-    @Getter
-    private transient Map<Integer, EvaluationResult> results = new LinkedHashMap<>();
-
     /**
      * All resources needed to run the evaluation.
      *
@@ -129,6 +112,11 @@ public final class Evaluation extends CompositeIndicator {
     @Getter
     private final ResourceManager resourceManager;
 
+    /**
+     * Flag to ignore when climatic data is empty for a phase.
+     */
+    private boolean ignoreEmptyClimaticData = false;
+
     /**
      * Flag for state Saved.
      */
@@ -198,12 +186,7 @@ public final class Evaluation extends CompositeIndicator {
                 throw new RuntimeException("This should never occur!", ex);
             }
         }
-        if (evaluation.computedPhases != null) {
-            computedPhases = new ArrayList<>();
-            computedPhases.addAll(evaluation.computedPhases);
-        }
         isTranscient = evaluation.isTranscient;
-        results = evaluation.results;
         state = evaluation.state;
     }
 
@@ -285,11 +268,26 @@ public final class Evaluation extends CompositeIndicator {
     }
 
     /**
-     * Clear results of indicators.
+     * Check data before running compute* methods.
+     *
+     * @param phases phenological phases to check
+     * @param climaticResource climatic resource to check
+     * @throws IndicatorsException exception
      */
-    private void clearResults() {
-        results.clear();
-        computedPhases = new ArrayList<>();
+    private void checkBeforeCompute(final List<CompositeIndicator> phases, final ClimaticResource climaticResource)
+            throws IndicatorsException {
+        if (phases == null) {
+            throw new RuntimeException("Phase list is null!");
+        }
+        if (phases.isEmpty()) {
+            throw new RuntimeException("Phase list is empty!");
+        }
+        if (climaticResource.isEmpty()) {
+            throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY);
+        }
+        if (resourceManager.getPhenologicalResource().isEmpty()) {
+            throw new IndicatorsException(ResourceErrorType.PHENO_EMPTY);
+        }
     }
 
     @Override
@@ -300,32 +298,30 @@ public final class Evaluation extends CompositeIndicator {
     /**
      * Compute indicator results.
      *
+     * @return Results of computation by year.
      * @throws IndicatorsException
      *             from Indicator.compute()
      */
-    public void compute() throws IndicatorsException {
+    public Map<Integer, EvaluationResult> compute() throws IndicatorsException {
         LOGGER.trace("start computing evaluation \"" + getName() + "\"");
+        this.ignoreEmptyClimaticData = false;
         fireIndicatorEvent(IndicatorEvent.Type.COMPUTE_START.event(this));
 
         final List<CompositeIndicator> phases = getPhases();
-        if (phases == null) {
-            throw new RuntimeException("Phase list is null!");
-        }
-        if (phases.isEmpty()) {
-            throw new RuntimeException("Phase list is empty!");
-        }
-        if (resourceManager.getClimaticResource().isEmpty()) {
-            throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY);
-        }
-        if (resourceManager.getPhenologicalResource().isEmpty()) {
-            throw new IndicatorsException(ResourceErrorType.PHENO_EMPTY);
-        }
+        final ClimaticResource climaticResource = resourceManager.getClimaticResource();
+        checkBeforeCompute(phases, climaticResource);
+
+        var results = compute(climaticResource, phases);
+        fireIndicatorEvent(IndicatorEvent.Type.COMPUTE_SUCCESS.event(this));
+        return results;
+    }
 
-        clearResults();
+    private Map<Integer, EvaluationResult> compute(final ClimaticResource climaticResource,
+            final List<CompositeIndicator> phases) throws IndicatorsException {
+        final Map<Integer, EvaluationResult> results = new LinkedHashMap<>();
 
         /* Pour chaque phase */
-        List<AnnualStageData> stageDatas;
-        stageDatas = getResourceManager().getPhenologicalResource().getData();
+        final List<AnnualStageData> stageDatas = getResourceManager().getPhenologicalResource().getData();
         for (final CompositeIndicator phase : phases) {
             final String phaseId = phase.getId();
             if (phaseId == null) {
@@ -348,8 +344,7 @@ public final class Evaluation extends CompositeIndicator {
                     dateYear -= 1;
                 }
 
-                if (!resourceManager.getClimaticResource().getYears()
-                        .contains(dateYear)) {
+                if (!climaticResource.getYears().contains(dateYear)) {
                     continue;
                 }
 
@@ -368,7 +363,6 @@ public final class Evaluation extends CompositeIndicator {
                 annualPhase.setEndStage(endStageName);
                 annualPhase.setStartStage(startStageName);
                 annualPhase.setUid(phaseId);
-                computedPhases.add(annualPhase);
                 final PhaseResult phaseResult = new PhaseResult();
                 phaseResult.setAnnualPhase(annualPhase);
                 results.get(year).getPhaseResults().add(phaseResult);
@@ -399,15 +393,19 @@ public final class Evaluation extends CompositeIndicator {
                 }
 
                 /* Données climatiques pendant la phase et l'année donnée */
-                final ClimaticResource climaticData = resourceManager
-                        .getClimaticResource().getClimaticDataByPhaseAndYear(startDate, endDate);
+                final ClimaticResource climaticData = climaticResource
+                        .getClimaticDataByPhaseAndYear(startDate, endDate);
                 if (climaticData.isEmpty()) {
-                    if (resourceManager.getClimaticResource().isEmpty()) {
+                    if (climaticResource.isEmpty()) {
                         throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY);
                     }
+                    if (ignoreEmptyClimaticData) {
+                        continue;
+                    }
                     final int yearToSearch = dateYear;
-                    final List<ClimaticDailyData> ddataList = resourceManager.getClimaticResource().getData()
-                            .stream().filter(f -> f.getYear() == yearToSearch).collect(Collectors.toList());
+                    final List<ClimaticDailyData> ddataList = climaticResource.getData().stream() //
+                            .filter(f -> f.getYear() == yearToSearch) //
+                            .collect(Collectors.toList());
 
                     final ClimaticDailyData startData = ddataList.get(0);
                     final ClimaticDailyData endData = ddataList.get(ddataList.size() - 1);
@@ -433,9 +431,43 @@ public final class Evaluation extends CompositeIndicator {
             }
         }
 
-        computeFaisability();
-        fireIndicatorEvent(IndicatorEvent.Type.COMPUTE_SUCCESS.event(this));
+        computeFaisability(results);
         LOGGER.trace("end of computing evaluation \"{}\"", getName());
+        return results;
+    }
+
+    /**
+     * Compute indicator results for each step of provided climatic data.
+     *
+     * @return Results of computation by year, for each step in climatic data.
+     * @throws IndicatorsException
+     *             from Indicator.compute()
+     */
+    public Map<Date, Map<Integer, EvaluationResult>> computeEach() throws IndicatorsException {
+        LOGGER.info("Start");
+        this.ignoreEmptyClimaticData = true;
+        final List<CompositeIndicator> phases = getPhases();
+        final ClimaticResource climaticResource = resourceManager.getClimaticResource();
+        checkBeforeCompute(phases, climaticResource);
+
+        final List<ClimaticDailyData> dailyData = climaticResource.getData();
+        // first, create Maps
+        final Map<Date, Map<Integer, EvaluationResult>> allResults = new LinkedHashMap<>();
+        //dailyData.stream().map(DailyData::getDate).forEach(date -> allResults.put(date, new LinkedHashMap<>()));
+        // then compute
+        final ClimaticResource resource = new ClimaticResource();
+        resource.setMissingVariables(climaticResource.getMissingVariables());
+        for (int i = 0; i < dailyData.size(); i++) {
+            final Date date = dailyData.get(i).getDate();
+            LOGGER.info("Compute {}", date);
+            final List<ClimaticDailyData> data = dailyData.subList(0, i);
+            resource.setData(data);
+            var results = compute(resource, phases);
+            LOGGER.info("Results : {}", results);
+            allResults.put(date, results);
+        }
+        LOGGER.info("End");
+        return allResults;
     }
 
     /**
@@ -448,9 +480,10 @@ public final class Evaluation extends CompositeIndicator {
      * Pour chaque année, Pour chaque phase, valeurs.onIndicatorAdd(valeur phase
      * p, année n); Fin pour aggregation(valeurs); Fin pour;
      *
+     * @param results Results of computation by year.
      * @throws IndicatorsException raised by AggregationFunction.aggregate()
      */
-    private void computeFaisability() throws IndicatorsException {
+    private void computeFaisability(final Map<Integer, EvaluationResult> results) throws IndicatorsException {
         LOGGER.traceEntry();
         if (getType() == EvaluationType.WITHOUT_AGGREGATION) {
             return;
@@ -459,18 +492,14 @@ public final class Evaluation extends CompositeIndicator {
             // if only 1 phase, no need to aggregate
             // evaluation value = value of the phase
             results.values().forEach(result ->
-            result.setNormalizedValue(
-                    result.getPhaseResults().get(0).getNormalizedValue()
-                    )
-                    );
+                    result.setNormalizedValue(result.getPhaseResults().get(0).getNormalizedValue()));
             return;
         } else if (getAggregationFunction().getExpression() == null) {
             throw new IllegalStateException("An evaluation with more than 1 "
                     + "phase must have a defined expression for aggregation!");
         }
 
-        for (final Map.Entry<Integer, EvaluationResult> entry
-                : results.entrySet()) {
+        for (final Map.Entry<Integer, EvaluationResult> entry : results.entrySet()) {
             final EvaluationResult evaluationResult = entry.getValue();
             final int year = entry.getKey();
             if (evaluationResult == null) {
@@ -872,22 +901,6 @@ public final class Evaluation extends CompositeIndicator {
         return result;
     }
 
-    /**
-     * Context: Deserialization does not initialize results.
-     *
-     * A final field must be initialized either by direct assignment of an initial value or in the constructor. During
-     * deserialization, neither of these are invoked, so initial values for transients must be set in the
-     * 'readObject()' private method that's invoked during deserialization. And for that to work, the transients must
-     * be non-final.
-     *
-     * @param ois input stream from deserialization
-     */
-    private void readObject(final ObjectInputStream ois) throws ClassNotFoundException, IOException {
-        // perform the default de-serialization first
-        ois.defaultReadObject();
-        results = new HashMap<>();
-    }
-
     /**
      * Set parameters (id and attributes) for the indicator and its criteria
      * from knowledge defined in settings.
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
index c0af4e5e..e0b76d50 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
@@ -252,14 +252,14 @@ implements DataLoadingListener, Detailable, HasDataLoadingListener, Comparable<I
     }
 
     @Override
-    public final Double compute(final Resource<? extends DailyData> climRessource) throws IndicatorsException {
-        if (climRessource.getYears().isEmpty()) {
+    public final Double compute(final Resource<? extends DailyData> climResource) throws IndicatorsException {
+        if (climResource.getYears().isEmpty()) {
             throw new RuntimeException(
                     String.format(
                             "No years in ClimaticResource (%d dailyData)!",
-                            climRessource.getData().size()));
+                            climResource.getData().size()));
         }
-        final HashMap<String, Double> results = new HashMap<>();
+        final Map<String, Double> results = new HashMap<>();
         double valueAfterAggregation = 0;
         Double valueAfterNormalization;
 
@@ -274,7 +274,7 @@ implements DataLoadingListener, Detailable, HasDataLoadingListener, Comparable<I
             }
 
             try {
-                final double value = indicator.compute(climRessource);
+                final Double value = indicator.compute(climResource);
                 results.put(indicator.getId(), value);
             } catch (final IndicatorsException e) {
                 throw new IndicatorsException(ComputationErrorType.COMPOSITE_COMPUTATION, e, indicator.getId());
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Indicator.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Indicator.java
index 22969b6c..e383f79e 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Indicator.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Indicator.java
@@ -186,6 +186,7 @@ Serializable, UseVariables {
      * Normalized value.
      */
     @XmlTransient
+    @Getter
     @Setter
     private Double value;
 
@@ -225,8 +226,8 @@ Serializable, UseVariables {
             }
         }
         listeners.add(IndicatorListener.class, listener);
-        if (this instanceof CompositeIndicator) {
-            ((CompositeIndicator) this).getIndicators()
+        if (this instanceof CompositeIndicator compositeIndicator) {
+            compositeIndicator.getIndicators()
             .forEach(i -> i.addIndicatorListener(listener));
         }
     }
@@ -364,15 +365,6 @@ Serializable, UseVariables {
      */
     public abstract EvaluationType getType();
 
-    /**
-     * The normalized value.
-     *
-     * @return Normalized value
-     */
-    public final Double getValue() {
-        return value;
-    }
-
     /**
      * @param indicatorCategory indicator category
      */
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java
index f90f64f0..0de0bdb9 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java
@@ -2,8 +2,8 @@ package fr.inrae.agroclim.indicators.model;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.util.List;
@@ -52,16 +52,16 @@ public class EvaluationHourlyTest extends DataTestHelper {
     @Test
     public void compute() {
         assertTrue("Evaluation must not be null!", evaluation != null);
-        String error = null;
+        final Map<Integer, EvaluationResult> results;
         try {
             evaluation.initializeResources();
-            evaluation.compute();
+            results = evaluation.compute();
         } catch (final IndicatorsException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
 
-        final Map<Integer, EvaluationResult> results = evaluation.getResults();
         assertNotNull(results);
         assertEquals(1, results.keySet().size());
         assertTrue(results.containsKey(2015));
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java
index 8e99a6bc..38d60322 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java
@@ -20,8 +20,8 @@ package fr.inrae.agroclim.indicators.model;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.util.Arrays;
@@ -80,16 +80,16 @@ public class EvaluationRobertTest extends DataTestHelper {
     @Test
     public void compute() {
         assertTrue("Evaluation must not be null!", evaluation != null);
-        String error = null;
+        final Map<Integer, EvaluationResult> results;
         try {
             evaluation.initializeResources();
-            evaluation.compute();
+            results = evaluation.compute();
         } catch (final IndicatorsException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
 
-        final Map<Integer, EvaluationResult> results = evaluation.getResults();
         assertNotNull(results);
         assertEquals(2, results.keySet().size());
         assertTrue(results.containsKey(1991));
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
index a3f975d4..85bf316e 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -49,6 +50,7 @@ import fr.inrae.agroclim.indicators.model.indicator.Indicator;
 import fr.inrae.agroclim.indicators.model.result.EvaluationResult;
 import fr.inrae.agroclim.indicators.model.result.IndicatorResult;
 import fr.inrae.agroclim.indicators.model.result.PhaseResult;
+import java.util.Date;
 import lombok.NonNull;
 import lombok.extern.log4j.Log4j2;
 
@@ -128,6 +130,32 @@ public final class EvaluationTest extends DataTestHelper {
         assertNull(error, error);
     }
 
+    /**
+     * Check if evaluation computes.
+     */
+    @Test
+    public void computeEach() {
+        assertTrue("Evaluation must not be null!", evaluation != null);
+        final Map<Date, Map<Integer, EvaluationResult>> results;
+        try {
+            evaluation.initializeResources();
+            results = evaluation.computeEach();
+        } catch (final IndicatorsException e) {
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
+        }
+        assertNotNull(results);
+
+        assertEquals(evaluation.getClimaticResource().getData().size(), results.size());
+        final List<Integer> years = results.values().stream().flatMap(m -> m.keySet().stream()).distinct().toList();
+        assertNotNull(years);
+        System.out.println(years);
+        assertEquals(1, years.size());
+        final Integer expectedYear = 2015;
+        assertEquals(expectedYear, years.get(0));
+    }
+
     @Test
     public void computableWithNoData() {
     	final File xmlFile = getEvaluationWithNoDataTestFile();
@@ -184,8 +212,11 @@ public final class EvaluationTest extends DataTestHelper {
         String error = null;
         try {
             evaluation.initializeResources();
-            evaluation.compute();
-            final List<AnnualPhase> phases = evaluation.getComputedPhases();
+            final Map<Integer, EvaluationResult> results = evaluation.compute();
+            final List<AnnualPhase> phases = results.values().stream() //
+                    .flatMap(r -> r.getPhaseResults().stream()) //
+                    .map(r -> r.getAnnualPhase()) //
+                    .toList();
             final int nbOfPhases = evaluation.getIndicators().size();
             final int nbOfYears = evaluation.getClimaticResource().getYears().size();
             assertEquals(nbOfPhases * nbOfYears, phases.size());
@@ -238,8 +269,7 @@ public final class EvaluationTest extends DataTestHelper {
         final String fmt = "%s | %4d | %s-%s | %f | %f";
         try {
             evaluation.initializeResources();
-            evaluation.compute();
-            final Map<Integer, EvaluationResult> results = evaluation.getResults();
+            final Map<Integer, EvaluationResult> results = evaluation.compute();
             assertNotNull("Results must not be null!", results);
             results.entrySet().forEach((entryER) -> {
                 final Integer year = entryER.getKey();
@@ -420,15 +450,16 @@ public final class EvaluationTest extends DataTestHelper {
     public void setParameters() {
         assertTrue("Evaluation must not be null!", evaluation != null);
         String error = null;
+        Map<Integer, EvaluationResult> results;
         try {
             evaluation.initializeResources();
-            evaluation.compute();
+            results = evaluation.compute();
         } catch (final IndicatorsException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
 
-        final Map<Integer, EvaluationResult> results = evaluation.getResults();
         final Double initValue = getResultValue("s0s1", "heat", 2015, results);
         assertNotNull("value of climatic effect \"heat\" must not be null", initValue);
 
@@ -438,11 +469,11 @@ public final class EvaluationTest extends DataTestHelper {
         parameters.put("Theat", 25.);
         evaluation.setParametersValues(parameters);
         try {
-            evaluation.compute();
+            results = evaluation.compute();
         } catch (IndicatorsException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
 
         final Double newValue = getResultValue("s0s1", "heat", 2015, results);
         assertNotEquals("init = " + initValue + ", new = " + newValue, initValue, newValue);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java
index d226c65e..d02a249d 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java
@@ -20,8 +20,8 @@ package fr.inrae.agroclim.indicators.model;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.util.Map;
@@ -63,15 +63,15 @@ public class EvalutationCustomHeadersTest extends DataTestHelper {
     @Test
     public void compute() {
         assertTrue("Evaluation must not be null!", evaluation != null);
-        String error = null;
+        final Map<Integer, EvaluationResult> results;
         try {
             evaluation.initializeResources();
-            evaluation.compute();
+            results = evaluation.compute();
         } catch (final IndicatorsException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
-        final Map<Integer, EvaluationResult> results = evaluation.getResults();
         assertNotNull("Results must not be null!", results);
         assertEquals("One year en climate file", 1, results.keySet().size());
         results.values().stream().flatMap(v -> v.getPhaseResults().stream()).forEach(phaseResult -> {
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java
index 82613882..a102b23a 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java
@@ -17,7 +17,6 @@
 package fr.inrae.agroclim.indicators.model;
 
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
@@ -35,6 +34,7 @@ import fr.inrae.agroclim.indicators.model.result.EvaluationResult;
 import fr.inrae.agroclim.indicators.model.result.IndicatorResult;
 import fr.inrae.agroclim.indicators.model.result.PhaseResult;
 import lombok.extern.log4j.Log4j2;
+import static org.junit.Assert.fail;
 
 /**
  * Test that Marine Marjou's Getari file computes.
@@ -76,16 +76,17 @@ public class MMarjouTest extends DataTestHelper {
     @Test
     public void raidaysInfNbDaysInPhase() {
         assertTrue("Evaluation must not be null!", evaluation != null);
-        String error = null;
+        final Map<Integer, EvaluationResult> results;
         try {
             evaluation.initializeResources();
             long start = System.currentTimeMillis();
-            evaluation.compute();
+            results = evaluation.compute();
             LOGGER.info("Evaluation.compute() last {} seconds", (System.currentTimeMillis() - start) / 1000.);
         } catch (final IndicatorsException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
         // extract stages to compute number of days
         Map<Integer, Map<String, Integer>> stageDates = new HashMap<>();
         List<AnnualStageData> aSDatas;
@@ -100,9 +101,8 @@ public class MMarjouTest extends DataTestHelper {
             });
         });
         // check excraidays, hraidays and raidays
-        assertFalse(evaluation.getResults().isEmpty());
+        assertFalse(results.isEmpty());
         String fmt = "%d | %s %s : %d | %s=%.4f";
-        Map<Integer, EvaluationResult> results = evaluation.getResults();
         for (Map.Entry<Integer, EvaluationResult> entry : results.entrySet()) {
             Integer year = entry.getKey();
             if (!stageDates.containsKey(year)) {
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java
index c9c8ba33..c31942d7 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java
@@ -19,8 +19,8 @@ package fr.inrae.agroclim.indicators.model;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.io.IOException;
@@ -208,8 +208,8 @@ public class RaidayMeantTest extends DataTestHelper {
     @Test
     public void computeUsingPhenologyCalculator() {
         assertTrue("Evaluation must not be null!", evaluation != null);
-        String error = null;
-        List<AnnualStageData> annualStageDatas = null;
+        final List<AnnualStageData> annualStageDatas;
+        final Map<Integer, EvaluationResult> results;
         try {
             final EvaluationSettings settings = evaluation.getSettings();
             // wheat Soissons
@@ -221,25 +221,29 @@ public class RaidayMeantTest extends DataTestHelper {
             settings.getPhenologyLoader().setCalculator(calc);
             settings.getPhenologyLoader().setFile(null);
             evaluation.initializeResources();
-            evaluation.compute();
+            results = evaluation.compute();
             annualStageDatas = evaluation.getResourceManager().getPhenologicalResource().getData();
         } catch (final IndicatorsException | IOException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
 
         // first year in phenology file
         final int firstYear = 1959;
         assertNotNull(annualStageDatas);
         assertFalse(annualStageDatas.isEmpty());
         assertTrue(firstYear == annualStageDatas.get(0).getYear());
-        final List<AnnualPhase> computedPhases = evaluation.getComputedPhases();
+        final List<AnnualPhase> computedPhases = results.values().stream() //
+                .flatMap(r -> r.getPhaseResults().stream()) //
+                .map(r -> r.getAnnualPhase()) //
+                .toList();
         assertNotNull(computedPhases);
         assertFalse(computedPhases.isEmpty());
         assertTrue(firstYear == computedPhases.get(0).getHarvestYear());
         compareStagesAndPhases(annualStageDatas, computedPhases);
 
-        assertFalse(evaluation.getResults().isEmpty());
+        assertFalse(results.isEmpty());
     }
 
     /**
@@ -248,14 +252,15 @@ public class RaidayMeantTest extends DataTestHelper {
     @Test
     public void compute() {
         assertTrue("Evaluation must not be null!", evaluation != null);
-        String error = null;
+        final Map<Integer, EvaluationResult> results;
         try {
             evaluation.initializeResources();
-            evaluation.compute();
+            results = evaluation.compute();
         } catch (final IndicatorsException e) {
-            error = e.getClass() + " : " + e.getLocalizedMessage();
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
         }
-        assertNull(error, error);
         // extract stages to compute number of days
         final Map<Integer, Map<String, Integer>> stageDates = new HashMap<>();
         List<AnnualStageData> aSDatas;
@@ -271,9 +276,9 @@ public class RaidayMeantTest extends DataTestHelper {
         });
 
         // check excraidays, hraidays and raidays
-        assertFalse(evaluation.getResults().isEmpty());
+        assertFalse(results.isEmpty());
         final String fmt = "%d | %s %s : %d | %s=%.4f";
-        for (final Map.Entry<Integer, EvaluationResult> entry : evaluation.getResults().entrySet()) {
+        for (final Map.Entry<Integer, EvaluationResult> entry : results.entrySet()) {
             final Integer year = entry.getKey();
             if (!stageDates.containsKey(year)) {
                 LOGGER.trace("No stage date for year {}", year);
@@ -317,14 +322,16 @@ public class RaidayMeantTest extends DataTestHelper {
         }
 
         assertNotNull(aSDatas);
-        final List<AnnualPhase> computedPhases = evaluation.getComputedPhases();
+        final List<AnnualPhase> computedPhases = results.values().stream() //
+                .flatMap(r -> r.getPhaseResults().stream()) //
+                .map(r -> r.getAnnualPhase()) //
+                .toList();
         compareStagesAndPhases(aSDatas, computedPhases);
 
-        Map<String, Map<Integer, Map<String, Double>>> computation;
-        computation = new HashMap<>();
+        final Map<String, Map<Integer, Map<String, Double>>> computation = new HashMap<>();
 
-        evaluation.getResults().forEach((year, results) -> {
-            results.getPhaseResults().forEach((phase) -> {
+        results.forEach((year, r) -> {
+            r.getPhaseResults().forEach((phase) -> {
                 final String phaseId = phase.getPhaseId();
                 if (!computation.containsKey(phaseId)) {
                     computation.put(phaseId, new HashMap<>());
-- 
GitLab


From b8b17e4028f3b1cd056d134a32e055453bb8c44e Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Mon, 19 Aug 2024 14:23:29 +0200
Subject: [PATCH 08/15] Ajouter des tests

---
 .../agroclim/indicators/model/Evaluation.java |  14 +-
 .../model/result/EvaluationResult.java        |   2 +
 .../model/EvaluationEachDateTest.java         | 134 ++++++++++++++++++
 .../indicators/model/EvaluationTest.java      |  27 ----
 .../indicators/xml/evaluation-phalen.gri      | 100 +++++++++++++
 5 files changed, 242 insertions(+), 35 deletions(-)
 create mode 100644 src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
 create mode 100644 src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri

diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
index 31baba23..8dceebc1 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
@@ -32,6 +32,7 @@ import java.util.stream.Collectors;
 
 import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType;
+import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.model.data.ResourceManager;
 import fr.inrae.agroclim.indicators.model.data.Variable;
 import fr.inrae.agroclim.indicators.model.data.Variable.Type;
@@ -439,12 +440,12 @@ public final class Evaluation extends CompositeIndicator {
     /**
      * Compute indicator results for each step of provided climatic data.
      *
-     * @return Results of computation by year, for each step in climatic data.
+     * @return Results of computation by year, for each step in climatic data:
+     * Climatic date ⮕ (year, {@link EvalutationResult}).
      * @throws IndicatorsException
      *             from Indicator.compute()
      */
-    public Map<Date, Map<Integer, EvaluationResult>> computeEach() throws IndicatorsException {
-        LOGGER.info("Start");
+    public Map<Date, Map<Integer, EvaluationResult>> computeEachDate() throws IndicatorsException {
         this.ignoreEmptyClimaticData = true;
         final List<CompositeIndicator> phases = getPhases();
         final ClimaticResource climaticResource = resourceManager.getClimaticResource();
@@ -453,20 +454,17 @@ public final class Evaluation extends CompositeIndicator {
         final List<ClimaticDailyData> dailyData = climaticResource.getData();
         // first, create Maps
         final Map<Date, Map<Integer, EvaluationResult>> allResults = new LinkedHashMap<>();
-        //dailyData.stream().map(DailyData::getDate).forEach(date -> allResults.put(date, new LinkedHashMap<>()));
+        dailyData.stream().map(DailyData::getDate).forEach(date -> allResults.put(date, new LinkedHashMap<>()));
         // then compute
         final ClimaticResource resource = new ClimaticResource();
         resource.setMissingVariables(climaticResource.getMissingVariables());
         for (int i = 0; i < dailyData.size(); i++) {
             final Date date = dailyData.get(i).getDate();
-            LOGGER.info("Compute {}", date);
             final List<ClimaticDailyData> data = dailyData.subList(0, i);
             resource.setData(data);
-            var results = compute(resource, phases);
-            LOGGER.info("Results : {}", results);
+            final Map<Integer, EvaluationResult> results = compute(resource, phases);
             allResults.put(date, results);
         }
-        LOGGER.info("End");
         return allResults;
     }
 
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/result/EvaluationResult.java b/src/main/java/fr/inrae/agroclim/indicators/model/result/EvaluationResult.java
index 70782bf1..d0fba97c 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/result/EvaluationResult.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/result/EvaluationResult.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import lombok.Getter;
+import lombok.ToString;
 
 /**
  * Normalized value of an evaluation for a year and values of composed phases.
@@ -30,6 +31,7 @@ import lombok.Getter;
  * @author $Author$
  * @version $Revision$
  */
+@ToString
 public class EvaluationResult extends Result {
 
     /**
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
new file mode 100644
index 00000000..5b8c6daa
--- /dev/null
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
@@ -0,0 +1,134 @@
+package fr.inrae.agroclim.indicators.model;
+
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
+import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
+import fr.inrae.agroclim.indicators.model.indicator.Indicator;
+import fr.inrae.agroclim.indicators.model.result.EvaluationResult;
+import fr.inrae.agroclim.indicators.model.result.IndicatorResult;
+import fr.inrae.agroclim.indicators.model.result.PhaseResult;
+import fr.inrae.agroclim.indicators.util.DateUtils;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test {@link Evaluation#computeEachDate()}.
+ *
+ * @author Olivier Maury
+ */
+public class EvaluationEachDateTest extends DataTestHelper {
+
+    /**
+     * Evaluation from good XML file.
+     */
+    private Evaluation evaluation;
+
+    /**
+     * Set up Evaluation to test.
+     */
+    @Before
+    public void beforeTest() {
+        final File xmlFile = getFile("xml/evaluation-phalen.gri");
+        evaluation = getEvaluation(xmlFile);
+    }
+
+    /**
+     * Check if evaluation computes.
+     */
+    @Test
+    public void computeEachDate() {
+        assertTrue("Evaluation must not be null!", evaluation != null);
+        final Map<Date, Map<Integer, EvaluationResult>> results;
+        try {
+            evaluation.initializeResources();
+            results = evaluation.computeEachDate();
+        } catch (final IndicatorsException e) {
+            final String error = e.getClass() + " : " + e.getLocalizedMessage();
+            fail(error);
+            return;
+        }
+        assertNotNull(results);
+
+        assertEquals(evaluation.getClimaticResource().getData().size(), results.size());
+        final List<Integer> years = results.values().stream().flatMap(m -> m.keySet().stream()).distinct().toList();
+        assertNotNull(years);
+        assertEquals(1, years.size());
+        final Integer expectedYear = 2015;
+        assertEquals(expectedYear, years.get(0));
+
+        // s0s1 : phalen
+        final List<Indicator> indicators = new ArrayList<>();
+        final List<Indicator> allIndicators = new ArrayList<>();
+        allIndicators.addAll(evaluation.getIndicators());
+        Indicator indicator = allIndicators.remove(0);
+        while (indicator != null) {
+            if (indicator instanceof CompositeIndicator c) {
+                allIndicators.addAll(c.getIndicators());
+            } else {
+                indicators.add(indicator);
+            }
+            if (!allIndicators.isEmpty()) {
+                indicator = allIndicators.remove(0);
+            } else {
+                indicator = null;
+            }
+        }
+        final int nbOfIndicators = indicators.size();
+        assertEquals(1, nbOfIndicators);
+
+        // no data on 1st january 2015 as no phase
+        final Optional<Date> firstDateOptional = results.keySet().stream().findFirst();
+        assertTrue(firstDateOptional.isPresent());
+        final Date firstDate = firstDateOptional.get();
+        final Date expectedFirstDate = DateUtils.getDate(expectedYear, 1);
+        assertEquals(expectedFirstDate, firstDate);
+        assertFalse(results.get(firstDate).containsKey(expectedYear));
+
+        // s0s1 : 120 − 140
+        final int s0 = 120;
+        final int s0Plus1 = s0 + 1;
+        final int s1 = 140;
+        List.of(s0Plus1, s1).forEach(doy -> {
+            final Date date = DateUtils.getDate(expectedYear, doy);
+            final List<PhaseResult> phaseResults = results.get(date).get(expectedYear).getPhaseResults();
+            assertFalse(phaseResults.isEmpty());
+            assertEquals(1, phaseResults.size());
+            final List<IndicatorResult> processResults = phaseResults.get(0).getIndicatorResults();
+            assertNotNull("On " + date + "/" + doy +", process results must not be null!", processResults);
+            assertEquals(1, processResults.size());
+            assertEquals("growth", processResults.get(0).getIndicatorId());
+
+            // practices > growth > phalength > phalen
+            final List<IndicatorResult> practicesResults = processResults.get(0).getIndicatorResults();
+            assertEquals("phalength", practicesResults.get(0).getIndicatorId());
+            List<IndicatorResult> indicatorResults = practicesResults.get(0).getIndicatorResults();
+            assertEquals("phalen", indicatorResults.get(0).getIndicatorId());
+            Double value = indicatorResults.get(0).getRawValue();
+            assertNotNull("On " + date + "/" + doy + ", phalen must not be null!", value);
+        });
+
+        // Check values
+        for (int doy = s0Plus1; doy < s1; doy++) {
+            final Date date = DateUtils.getDate(expectedYear, doy);
+            final List<PhaseResult> phaseResults = results.get(date).get(expectedYear).getPhaseResults();
+            final List<IndicatorResult> processResults = phaseResults.get(0).getIndicatorResults();
+            final List<IndicatorResult> practicesResults = processResults.get(0).getIndicatorResults();
+            final List<IndicatorResult> indicatorResults = practicesResults.get(0).getIndicatorResults();
+            final Double value = indicatorResults.get(0).getRawValue();
+            assertNotNull("On " + date + "/" + doy + ", phalen must not be null!", value);
+            final Double expected = Double.valueOf(doy - s0);
+            assertEquals("On " + date + "/" + doy + ", phalen must equal " + expected, expected, value);
+        }
+    }
+}
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
index 85bf316e..0603a053 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
@@ -50,7 +50,6 @@ import fr.inrae.agroclim.indicators.model.indicator.Indicator;
 import fr.inrae.agroclim.indicators.model.result.EvaluationResult;
 import fr.inrae.agroclim.indicators.model.result.IndicatorResult;
 import fr.inrae.agroclim.indicators.model.result.PhaseResult;
-import java.util.Date;
 import lombok.NonNull;
 import lombok.extern.log4j.Log4j2;
 
@@ -130,32 +129,6 @@ public final class EvaluationTest extends DataTestHelper {
         assertNull(error, error);
     }
 
-    /**
-     * Check if evaluation computes.
-     */
-    @Test
-    public void computeEach() {
-        assertTrue("Evaluation must not be null!", evaluation != null);
-        final Map<Date, Map<Integer, EvaluationResult>> results;
-        try {
-            evaluation.initializeResources();
-            results = evaluation.computeEach();
-        } catch (final IndicatorsException e) {
-            final String error = e.getClass() + " : " + e.getLocalizedMessage();
-            fail(error);
-            return;
-        }
-        assertNotNull(results);
-
-        assertEquals(evaluation.getClimaticResource().getData().size(), results.size());
-        final List<Integer> years = results.values().stream().flatMap(m -> m.keySet().stream()).distinct().toList();
-        assertNotNull(years);
-        System.out.println(years);
-        assertEquals(1, years.size());
-        final Integer expectedYear = 2015;
-        assertEquals(expectedYear, years.get(0));
-    }
-
     @Test
     public void computableWithNoData() {
     	final File xmlFile = getEvaluationWithNoDataTestFile();
diff --git a/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri b/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri
new file mode 100644
index 00000000..5d756803
--- /dev/null
+++ b/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!DOCTYPE evaluationSettings PUBLIC
+    "-//INRAE AgroClim.//DTD Evaluation 1.1//EN"
+    "https://agroclim.inrae.fr/getari/dtd/1.1/evaluation.dtd">
+<evaluationSettings timescale="DAILY" timestamp="2024-08-19T13:43:21.127972376" version="1.2.2+20230809135003">
+    <climate>
+        <file path="../model/data/climate/climate-2015.csv">
+            <separator>	</separator>
+            <header></header>
+            <header>year</header>
+            <header>month</header>
+            <header>day</header>
+            <header>tmin</header>
+            <header>tmax</header>
+            <header>tmean</header>
+            <header></header>
+            <header>radiation</header>
+            <header>rain</header>
+            <header>rh</header>
+            <header>wind</header>
+            <header>ETP</header>
+            <midnight>0</midnight>
+        </file>
+    </climate>
+    <notes>
+        <note>
+            <id>10.1016/j.eja.2019.125960</id>
+            <description>Future development of apricot blossom blight under climate change in Southern France</description>
+        </note>
+        <note>
+            <id>978-2-7148-0083-1</id>
+            <description>La ventilation des bâtiments d'élevage de ruminants, Institut du Végétal</description>
+        </note>
+    </notes>
+    <phenology>
+        <file path="../model/data/phenology/pheno_sample.csv">
+            <separator>;</separator>
+            <header>year</header>
+            <header>s0</header>
+            <header>s1</header>
+            <header>s2</header>
+            <header>s3</header>
+            <header>s4</header>
+        </file>
+    </phenology>
+    <name>evaluation-test</name>
+    <type>WITH_AGGREGATION</type>
+    <compositeIndicator>
+        <name>root-test</name>
+        <name xml:lang="en">evaluation-test</name>
+        <id>root-evaluation</id>
+        <timescale>DAILY</timescale>
+        <tag>practices</tag>
+        <indicator xsi:type="compositeIndicator" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+            <name>s1</name>
+            <id>s0s1</id>
+            <category>pheno</category>
+            <color>#5F9EA0</color>
+            <timescale>DAILY</timescale>
+            <tag>pheno-s0-s1</tag>
+            <indicator xsi:type="compositeIndicator">
+                <name>s0</name>
+                <id>pheno_s0</id>
+                <category>pheno</category>
+                <color>#5F9EA0</color>
+                <timescale>DAILY</timescale>
+                <normalizationFunction xsi:type="exponential" expA="0.0" expB="0.0"/>
+                <tag>pheno-s0-s1</tag>
+            </indicator>
+            <indicator xsi:type="compositeIndicator">
+                <name xml:lang="en">Crop growth</name>
+                <name xml:lang="fr">Développement cultural</name>
+                <id>growth</id>
+                <category>ecoprocesses</category>
+                <color>#5F9EA0</color>
+                <timescale>DAILY</timescale>
+                <indicator xsi:type="compositeIndicator">
+                    <name xml:lang="fr">Durée de la phase</name>
+                    <name xml:lang="en">Phase length</name>
+                    <id>phalength</id>
+                    <category>climatic</category>
+                    <color>#5F9EA0</color>
+                    <timescale>DAILY</timescale>
+                    <indicator xsi:type="phaseLength">
+                        <description xml:lang="fr">Durée de la phase.</description>
+                        <description xml:lang="en">Phase length.</description>
+                        <name xml:lang="fr">Durée de la phase</name>
+                        <name xml:lang="en">Phase length</name>
+                        <id>phalen</id>
+                        <category>indicator</category>
+                        <color>#5F9EA0</color>
+                        <timescale>DAILY</timescale>
+                        <normalizationFunction xsi:type="sigmoid" sigmoidA="30.0" sigmoidB="4.0"/>
+                        <unit>day</unit>
+                    </indicator>
+                </indicator>
+            </indicator>
+        </indicator>
+    </compositeIndicator>
+</evaluationSettings>
-- 
GitLab


From 74f51ae2848d02001a515af0873b29a5289a36e0 Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Mon, 19 Aug 2024 14:40:47 +0200
Subject: [PATCH 09/15] =?UTF-8?q?D=C3=A9couper=20le=20test=20en=20plusieur?=
 =?UTF-8?q?s=20m=C3=A9thodes?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../model/EvaluationEachDateTest.java         | 105 ++++++++++++------
 .../indicators/xml/evaluation-phalen.gri      |  16 +--
 2 files changed, 78 insertions(+), 43 deletions(-)

diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
index 5b8c6daa..61ded8b0 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
@@ -19,7 +19,7 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 /**
@@ -29,44 +29,74 @@ import org.junit.Test;
  */
 public class EvaluationEachDateTest extends DataTestHelper {
 
+    /**
+     * The only year in climatic data.
+     */
+    private static final Integer YEAR = 2015;
+
     /**
      * Evaluation from good XML file.
      */
-    private Evaluation evaluation;
+    private static Evaluation evaluation;
 
     /**
-     * Set up Evaluation to test.
+     * Computation results.
      */
-    @Before
-    public void beforeTest() {
-        final File xmlFile = getFile("xml/evaluation-phalen.gri");
-        evaluation = getEvaluation(xmlFile);
-    }
+    private static Map<Date, Map<Integer, EvaluationResult>> results;
+
+    /**
+     * DOY of Stage 0.
+     */
+    private static final int S0 = 120;
+
+    /**
+     * DOY of Stage 0 plus 1 day.
+     */
+    private static final int S0_PLUS_1 = S0 + 1;
+
+    /**
+     * DOY of Stage 1.
+     */
+    private static final int S1 = 140;
 
     /**
+     * Set up Evaluation to test.
+     *
      * Check if evaluation computes.
      */
-    @Test
-    public void computeEachDate() {
+    @BeforeClass
+    public static void beforeTest() {
+        final File xmlFile = getFile("xml/evaluation-phalen.gri");
+        evaluation = getEvaluation(xmlFile);
         assertTrue("Evaluation must not be null!", evaluation != null);
-        final Map<Date, Map<Integer, EvaluationResult>> results;
         try {
             evaluation.initializeResources();
             results = evaluation.computeEachDate();
         } catch (final IndicatorsException e) {
             final String error = e.getClass() + " : " + e.getLocalizedMessage();
             fail(error);
-            return;
         }
+    }
+
+    /**
+     * Ensure only 2015 has data.
+     */
+    @Test
+    public void years() {
         assertNotNull(results);
 
         assertEquals(evaluation.getClimaticResource().getData().size(), results.size());
         final List<Integer> years = results.values().stream().flatMap(m -> m.keySet().stream()).distinct().toList();
         assertNotNull(years);
         assertEquals(1, years.size());
-        final Integer expectedYear = 2015;
-        assertEquals(expectedYear, years.get(0));
+        assertEquals(YEAR, years.get(0));
+    }
 
+    /**
+     * Ensure only 1 indicator is computed.
+     */
+    @Test
+    public void indicators() {
         // s0s1 : phalen
         final List<Indicator> indicators = new ArrayList<>();
         final List<Indicator> allIndicators = new ArrayList<>();
@@ -86,26 +116,34 @@ public class EvaluationEachDateTest extends DataTestHelper {
         }
         final int nbOfIndicators = indicators.size();
         assertEquals(1, nbOfIndicators);
+    }
 
+    /**
+     * Ensure no data is returned out of s0s1.
+     */
+    @Test
+    public void noDataOutOfPhase() {
         // no data on 1st january 2015 as no phase
         final Optional<Date> firstDateOptional = results.keySet().stream().findFirst();
         assertTrue(firstDateOptional.isPresent());
         final Date firstDate = firstDateOptional.get();
-        final Date expectedFirstDate = DateUtils.getDate(expectedYear, 1);
+        final Date expectedFirstDate = DateUtils.getDate(YEAR, 1);
         assertEquals(expectedFirstDate, firstDate);
-        assertFalse(results.get(firstDate).containsKey(expectedYear));
-
-        // s0s1 : 120 − 140
-        final int s0 = 120;
-        final int s0Plus1 = s0 + 1;
-        final int s1 = 140;
-        List.of(s0Plus1, s1).forEach(doy -> {
-            final Date date = DateUtils.getDate(expectedYear, doy);
-            final List<PhaseResult> phaseResults = results.get(date).get(expectedYear).getPhaseResults();
+        assertFalse(results.get(firstDate).containsKey(YEAR));
+    }
+
+    /**
+     * Ensure data are return each date of s0s1.
+     */
+    @Test
+    public void dataInPhase() {
+        for (int doy = S0_PLUS_1; doy < S1; doy++) {
+            final Date date = DateUtils.getDate(YEAR, doy);
+            final List<PhaseResult> phaseResults = results.get(date).get(YEAR).getPhaseResults();
             assertFalse(phaseResults.isEmpty());
             assertEquals(1, phaseResults.size());
             final List<IndicatorResult> processResults = phaseResults.get(0).getIndicatorResults();
-            assertNotNull("On " + date + "/" + doy +", process results must not be null!", processResults);
+            assertNotNull("On " + date + "/" + doy + ", process results must not be null!", processResults);
             assertEquals(1, processResults.size());
             assertEquals("growth", processResults.get(0).getIndicatorId());
 
@@ -116,18 +154,23 @@ public class EvaluationEachDateTest extends DataTestHelper {
             assertEquals("phalen", indicatorResults.get(0).getIndicatorId());
             Double value = indicatorResults.get(0).getRawValue();
             assertNotNull("On " + date + "/" + doy + ", phalen must not be null!", value);
-        });
+        }
+    }
 
-        // Check values
-        for (int doy = s0Plus1; doy < s1; doy++) {
-            final Date date = DateUtils.getDate(expectedYear, doy);
-            final List<PhaseResult> phaseResults = results.get(date).get(expectedYear).getPhaseResults();
+    /**
+     * Check computed values for each date.
+     */
+    @Test
+    public void checkData() {
+        for (int doy = S0_PLUS_1; doy < S1; doy++) {
+            final Date date = DateUtils.getDate(YEAR, doy);
+            final List<PhaseResult> phaseResults = results.get(date).get(YEAR).getPhaseResults();
             final List<IndicatorResult> processResults = phaseResults.get(0).getIndicatorResults();
             final List<IndicatorResult> practicesResults = processResults.get(0).getIndicatorResults();
             final List<IndicatorResult> indicatorResults = practicesResults.get(0).getIndicatorResults();
             final Double value = indicatorResults.get(0).getRawValue();
             assertNotNull("On " + date + "/" + doy + ", phalen must not be null!", value);
-            final Double expected = Double.valueOf(doy - s0);
+            final Double expected = Double.valueOf(doy - S0);
             assertEquals("On " + date + "/" + doy + ", phalen must equal " + expected, expected, value);
         }
     }
diff --git a/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri b/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri
index 5d756803..b11858c3 100644
--- a/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri
+++ b/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri
@@ -2,7 +2,7 @@
 <!DOCTYPE evaluationSettings PUBLIC
     "-//INRAE AgroClim.//DTD Evaluation 1.1//EN"
     "https://agroclim.inrae.fr/getari/dtd/1.1/evaluation.dtd">
-<evaluationSettings timescale="DAILY" timestamp="2024-08-19T13:43:21.127972376" version="1.2.2+20230809135003">
+<evaluationSettings timescale="DAILY" timestamp="2024-08-19T14:26:01.505524546" version="2.0.1+20240405062628">
     <climate>
         <file path="../model/data/climate/climate-2015.csv">
             <separator>	</separator>
@@ -22,16 +22,7 @@
             <midnight>0</midnight>
         </file>
     </climate>
-    <notes>
-        <note>
-            <id>10.1016/j.eja.2019.125960</id>
-            <description>Future development of apricot blossom blight under climate change in Southern France</description>
-        </note>
-        <note>
-            <id>978-2-7148-0083-1</id>
-            <description>La ventilation des bâtiments d'élevage de ruminants, Institut du Végétal</description>
-        </note>
-    </notes>
+    <notes/>
     <phenology>
         <file path="../model/data/phenology/pheno_sample.csv">
             <separator>;</separator>
@@ -43,7 +34,7 @@
             <header>s4</header>
         </file>
     </phenology>
-    <name>evaluation-test</name>
+    <name>evaluation-phalen</name>
     <type>WITH_AGGREGATION</type>
     <compositeIndicator>
         <name>root-test</name>
@@ -51,6 +42,7 @@
         <id>root-evaluation</id>
         <timescale>DAILY</timescale>
         <tag>practices</tag>
+        <aggregationFunction xsi:type="jexlFunction" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
         <indicator xsi:type="compositeIndicator" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
             <name>s1</name>
             <id>s0s1</id>
-- 
GitLab


From 181043c6c7df8e16aaa6f86fe9fdb41189bf1542 Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Mon, 19 Aug 2024 15:00:23 +0200
Subject: [PATCH 10/15] =?UTF-8?q?feat:=20:boom:=20Ajouter=20une=20m=C3=A9t?=
 =?UTF-8?q?hode=20pour=20calculer=20toutes=20les=20valeurs=20correspondant?=
 =?UTF-8?q?=20au=20jeu=20climatique=20fourni.=20refs=20agroclim/agrometinf?=
 =?UTF-8?q?o/AgroMetInfo=5F2.0#35?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

refs agroclim/agrometinfo/AgroMetInfo_2.0#35 Renvoyer les résultats de calcul par la méthode `Evaluation.compute()` et ne plus les stocker dans `Evaluation`.
---
 pom.xml                               |  2 +-
 src/site/markdown/release-notes-fr.md | 28 +++++++++++++++++++++++++++
 src/site/site.xml                     |  1 +
 3 files changed, 30 insertions(+), 1 deletion(-)
 create mode 100644 src/site/markdown/release-notes-fr.md

diff --git a/pom.xml b/pom.xml
index 73b60187..8aaf73ca 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@ along with Indicators. If not, see <https://www.gnu.org/licenses/>.
     <name>Indicators</name>
     <description>Library of agro- and eco-climatic indicators.</description>
     <inceptionYear>2018</inceptionYear>
-    <version>2.0.3-SNAPSHOT</version>
+    <version>2.1.0-SNAPSHOT</version>
     <packaging>jar</packaging>
     <licenses>
         <license>
diff --git a/src/site/markdown/release-notes-fr.md b/src/site/markdown/release-notes-fr.md
new file mode 100644
index 00000000..e01a28e8
--- /dev/null
+++ b/src/site/markdown/release-notes-fr.md
@@ -0,0 +1,28 @@
+---
+title: Notes de version
+description: Modifications de la bibliothèque d'indicateurs.
+keywords: "version"
+date: 2024-08-19
+---
+
+# [v2.1.0](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.1.0) −
+
+- Ajouter la méthode `Evaluation.computeEachDate()`.
+- Renvoyer les résultats de calcul par la méthode `Evaluation.compute()` et ne plus les stocker dans `Evaluation`.
+
+# [v2.0.2](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.2) − 11 juillet 2024
+
+- Passer à Jakarta XML Binding.
+
+# [v2.0.1](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.1) − 5 avril 2024
+
+- Ajouter le modèle phénologique Richardson.
+- Renommer `CompositeIndicator.toAggregate(boolean)`.
+- Corriger l'id et la référence d'indicateurs THI.
+
+# [v2.0.0](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.0) − 18 janvier 2024
+
+- Gestion des données climatiques manquantes.
+- Changement de gestion des exceptions et affichage de codes d'erreurs.
+- Passage sous GitLab.
+- Mise en ligne de la documentation.
\ No newline at end of file
diff --git a/src/site/site.xml b/src/site/site.xml
index 0f490a99..89ea402e 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -30,6 +30,7 @@
             <item name="Indicateurs journaliers" href="indicators-daily-fr.html" />
             <item name="Codes d'erreurs" href="errors-fr.html" />
             <item name="Modèles phénologiques" href="pheno-fr.html" />
+            <item name="Notes de version" href="release-notes-fr.html" />
             <item name="Documentation in English" href="en/index.html" />
         </menu>
         <menu ref="modules" inherit="top" />
-- 
GitLab


From 7c1d888ea62aecd237983f3df595c18521ea204d Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Mon, 19 Aug 2024 15:49:06 +0200
Subject: [PATCH 11/15] Suppression de

---
 .../model/indicator/CompositeIndicator.java          | 12 ------------
 src/site/markdown/release-notes-fr.md                |  4 +++-
 2 files changed, 3 insertions(+), 13 deletions(-)

diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
index e0b76d50..b639fc70 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
@@ -651,18 +651,6 @@ implements DataLoadingListener, Detailable, HasDataLoadingListener, Comparable<I
         getIndicators().forEach(i -> i.setParametersValues(values));
     }
 
-    /**
-     * Detect if aggregation function is needed but missing.
-     *
-     * @param fire fire events while checking
-     * @return true if aggregation function is needed but missing
-     * @deprecated use {@link CompositeIndicator#isAggregationMissing(boolean)}.
-     */
-    @Deprecated(since = "2.0.1", forRemoval = true)
-    public final boolean toAggregate(final boolean fire) {
-        return isAggregationMissing(fire);
-    }
-
     @Override
     public final String toStringTree(final String indent) {
         final StringBuilder sb = new StringBuilder();
diff --git a/src/site/markdown/release-notes-fr.md b/src/site/markdown/release-notes-fr.md
index e01a28e8..9a462c8a 100644
--- a/src/site/markdown/release-notes-fr.md
+++ b/src/site/markdown/release-notes-fr.md
@@ -9,6 +9,7 @@ date: 2024-08-19
 
 - Ajouter la méthode `Evaluation.computeEachDate()`.
 - Renvoyer les résultats de calcul par la méthode `Evaluation.compute()` et ne plus les stocker dans `Evaluation`.
+- Suppression de `CompositeIndicator#toAggregate(boolean)`.
 
 # [v2.0.2](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.2) − 11 juillet 2024
 
@@ -19,10 +20,11 @@ date: 2024-08-19
 - Ajouter le modèle phénologique Richardson.
 - Renommer `CompositeIndicator.toAggregate(boolean)`.
 - Corriger l'id et la référence d'indicateurs THI.
+- Dépréciation `CompositeIndicator#toAggregate(boolean)`.
 
 # [v2.0.0](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.0) − 18 janvier 2024
 
 - Gestion des données climatiques manquantes.
 - Changement de gestion des exceptions et affichage de codes d'erreurs.
 - Passage sous GitLab.
-- Mise en ligne de la documentation.
\ No newline at end of file
+- Mise en ligne de la documentation.
-- 
GitLab


From 7fdc301c9f3e13a72d098ed35234d586c0146d1f Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Tue, 20 Aug 2024 09:31:27 +0200
Subject: [PATCH 12/15] Utiliser LocalDate

---
 .../agroclim/indicators/model/Evaluation.java     |  8 ++++----
 .../indicators/model/data/HourlyData.java         | 10 ++++++++++
 .../indicators/model/EvaluationEachDateTest.java  | 15 ++++++++-------
 3 files changed, 22 insertions(+), 11 deletions(-)

diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
index 8dceebc1..e228def7 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
@@ -445,7 +445,7 @@ public final class Evaluation extends CompositeIndicator {
      * @throws IndicatorsException
      *             from Indicator.compute()
      */
-    public Map<Date, Map<Integer, EvaluationResult>> computeEachDate() throws IndicatorsException {
+    public Map<LocalDate, Map<Integer, EvaluationResult>> computeEachDate() throws IndicatorsException {
         this.ignoreEmptyClimaticData = true;
         final List<CompositeIndicator> phases = getPhases();
         final ClimaticResource climaticResource = resourceManager.getClimaticResource();
@@ -453,16 +453,16 @@ public final class Evaluation extends CompositeIndicator {
 
         final List<ClimaticDailyData> dailyData = climaticResource.getData();
         // first, create Maps
-        final Map<Date, Map<Integer, EvaluationResult>> allResults = new LinkedHashMap<>();
-        dailyData.stream().map(DailyData::getDate).forEach(date -> allResults.put(date, new LinkedHashMap<>()));
+        final Map<LocalDate, Map<Integer, EvaluationResult>> allResults = new LinkedHashMap<>();
+        dailyData.stream().map(DailyData::getLocalDate).forEach(date -> allResults.put(date, new LinkedHashMap<>()));
         // then compute
         final ClimaticResource resource = new ClimaticResource();
         resource.setMissingVariables(climaticResource.getMissingVariables());
         for (int i = 0; i < dailyData.size(); i++) {
-            final Date date = dailyData.get(i).getDate();
             final List<ClimaticDailyData> data = dailyData.subList(0, i);
             resource.setData(data);
             final Map<Integer, EvaluationResult> results = compute(resource, phases);
+            final LocalDate date = dailyData.get(i).getLocalDate();
             allResults.put(date, results);
         }
         return allResults;
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/data/HourlyData.java b/src/main/java/fr/inrae/agroclim/indicators/model/data/HourlyData.java
index 5c6f5c28..97ecc147 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/data/HourlyData.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/data/HourlyData.java
@@ -21,6 +21,7 @@ import java.util.Calendar;
 import java.util.Date;
 
 import fr.inrae.agroclim.indicators.util.DateUtils;
+import java.time.LocalDate;
 import lombok.Getter;
 
 /**
@@ -174,6 +175,15 @@ public abstract class HourlyData implements Cloneable, Data, Serializable {
         return DateUtils.getDoy(getDate());
     }
 
+    /**
+     * Create date object with {@code day}, {@code month}, {@code year} and {@code hour} attribute.<br>
+     * If at least one of these attributes is null, then the returned date is null.
+     * @return date object
+     */
+    public LocalDate getLocalDate() {
+        return DateUtils.asLocalDate(getDate());
+    }
+
     /**
      * Get value for variable as it was set.
      *
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
index 61ded8b0..95dfba5c 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java
@@ -9,8 +9,9 @@ import fr.inrae.agroclim.indicators.model.result.IndicatorResult;
 import fr.inrae.agroclim.indicators.model.result.PhaseResult;
 import fr.inrae.agroclim.indicators.util.DateUtils;
 import java.io.File;
+import java.time.LocalDate;
+import java.time.Month;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -42,7 +43,7 @@ public class EvaluationEachDateTest extends DataTestHelper {
     /**
      * Computation results.
      */
-    private static Map<Date, Map<Integer, EvaluationResult>> results;
+    private static Map<LocalDate, Map<Integer, EvaluationResult>> results;
 
     /**
      * DOY of Stage 0.
@@ -124,10 +125,10 @@ public class EvaluationEachDateTest extends DataTestHelper {
     @Test
     public void noDataOutOfPhase() {
         // no data on 1st january 2015 as no phase
-        final Optional<Date> firstDateOptional = results.keySet().stream().findFirst();
+        final Optional<LocalDate> firstDateOptional = results.keySet().stream().findFirst();
         assertTrue(firstDateOptional.isPresent());
-        final Date firstDate = firstDateOptional.get();
-        final Date expectedFirstDate = DateUtils.getDate(YEAR, 1);
+        final LocalDate firstDate = firstDateOptional.get();
+        final LocalDate expectedFirstDate = LocalDate.of(YEAR, Month.JANUARY, 1);
         assertEquals(expectedFirstDate, firstDate);
         assertFalse(results.get(firstDate).containsKey(YEAR));
     }
@@ -138,7 +139,7 @@ public class EvaluationEachDateTest extends DataTestHelper {
     @Test
     public void dataInPhase() {
         for (int doy = S0_PLUS_1; doy < S1; doy++) {
-            final Date date = DateUtils.getDate(YEAR, doy);
+            final LocalDate date = DateUtils.asLocalDate(DateUtils.getDate(YEAR, doy));
             final List<PhaseResult> phaseResults = results.get(date).get(YEAR).getPhaseResults();
             assertFalse(phaseResults.isEmpty());
             assertEquals(1, phaseResults.size());
@@ -163,7 +164,7 @@ public class EvaluationEachDateTest extends DataTestHelper {
     @Test
     public void checkData() {
         for (int doy = S0_PLUS_1; doy < S1; doy++) {
-            final Date date = DateUtils.getDate(YEAR, doy);
+            final LocalDate date = DateUtils.asLocalDate(DateUtils.getDate(YEAR, doy));
             final List<PhaseResult> phaseResults = results.get(date).get(YEAR).getPhaseResults();
             final List<IndicatorResult> processResults = phaseResults.get(0).getIndicatorResults();
             final List<IndicatorResult> practicesResults = processResults.get(0).getIndicatorResults();
-- 
GitLab


From 8b5e9bfeebfce7959a7ae1104ee62415d77036e8 Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Wed, 21 Aug 2024 14:50:47 +0200
Subject: [PATCH 13/15] =?UTF-8?q?build(deps):=20:arrow=5Fup:=20passer=20?=
 =?UTF-8?q?=C3=A0=20maven-source-plugin-3.3.1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index 8aaf73ca..c354dd73 100644
--- a/pom.xml
+++ b/pom.xml
@@ -382,11 +382,11 @@ along with Indicators. If not, see <https://www.gnu.org/licenses/>.
                 </executions>
             </plugin>
             <!-- Attach source and javadoc artifacts -->
-            <!-- https://maven.apache.org/plugin-developers/cookbook/attach-source-javadoc-artifacts.html -->
+            <!-- https://maven.apache.org/plugins/maven-source-plugin/usage.html -->
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-source-plugin</artifactId>
-                <version>3.3.0</version>
+                <version>3.3.1</version>
                 <executions>
                     <execution>
                         <id>attach-sources</id>
-- 
GitLab


From 2d1244bf1414fe6f7cb5e752234aedca50ef7008 Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Wed, 21 Aug 2024 14:57:24 +0200
Subject: [PATCH 14/15] issue_template

---
 .gitlab/issue_templates/publier_nouvelle_version.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.gitlab/issue_templates/publier_nouvelle_version.md b/.gitlab/issue_templates/publier_nouvelle_version.md
index 908e8149..9ba22f2a 100644
--- a/.gitlab/issue_templates/publier_nouvelle_version.md
+++ b/.gitlab/issue_templates/publier_nouvelle_version.md
@@ -3,9 +3,10 @@ Pour passer de la version *SNAPSHOT* à la version stable :
 - [ ] créer une demande de fusion et une branche à partir du ticket
 - [ ] changer la version dans `pom.xml`
   ```sh
-  mvn versions:set -DnewVersion=2.0.2
+  mvn versions:set -DnewVersion=2.1.0
   mvn versions:commit
   ```
+- [ ] mettre à jour `src/site/markdown/release-notes-fr.md`
 - [ ] mettre à jour les fichiers de métadonnées du projet
 - [ ] fusionner
 - [ ] déployer sur Archiva
-- 
GitLab


From 6b3332c9f1c6585c6a713bef0a915ea1b3c60bda Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Wed, 21 Aug 2024 15:06:05 +0200
Subject: [PATCH 15/15] release notes

---
 src/site/markdown/release-notes-fr.md | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/site/markdown/release-notes-fr.md b/src/site/markdown/release-notes-fr.md
index 9a462c8a..d9c7ccf1 100644
--- a/src/site/markdown/release-notes-fr.md
+++ b/src/site/markdown/release-notes-fr.md
@@ -9,7 +9,9 @@ date: 2024-08-19
 
 - Ajouter la méthode `Evaluation.computeEachDate()`.
 - Renvoyer les résultats de calcul par la méthode `Evaluation.compute()` et ne plus les stocker dans `Evaluation`.
-- Suppression de `CompositeIndicator#toAggregate(boolean)`.
+- Supprimer `Evaluation#getResults()`.
+- Supprimer `Evaluation#getComputedPhases()`.
+- Supprimer de `CompositeIndicator#toAggregate(boolean)`.
 
 # [v2.0.2](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.2) − 11 juillet 2024
 
-- 
GitLab