Skip to content
Snippets Groups Projects
Commit 572eea6a authored by Olivier Maury's avatar Olivier Maury
Browse files

Réusinage pour ajout de tests

parent 75ba7b25
No related branches found
No related tags found
1 merge request!26Réusinage pour ajout de tests
Pipeline #227834 passed
......@@ -40,6 +40,7 @@
<junit.version>5.10.3</junit.version>
<log4j.version>2.23.1</log4j.version>
<lombok.version>1.18.34</lombok.version>
<mockito.version>5.12.0</mockito.version>
<picoli.version>4.7.6</picoli.version>
<season.version>1.3.1</season.version>
<!-- Maven environment values -->
......@@ -120,6 +121,12 @@
<version>2.2.224</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
......
/*
* Copyright (C) 2024 INRAE AgroClim
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.agrometinfo.seasonhandler;
import fr.inrae.agroclim.indicators.model.Evaluation;
import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
import fr.inrae.agroclim.season.core.dao.VarietyParameterDao;
import fr.inrae.agroclim.season.core.simulationproperties.SimulationProperties;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
/**
* Fit simulation properties and evaluation according to the number of years before sending to the queue.
*
* @author Olivier Maury
*/
@Log4j2
public class SimulationFitter {
public record Result(
Evaluation evaluation,
SimulationProperties simulationProperties
) { }
/**
* DAO to get the number of year of crops.
*/
@Setter
private VarietyParameterDao varietyParameterDao;
/**
* @param evaluation evaluation to send
* @param props simulation properties
* @param simulationDate requested date for the simulation
* @return fitted evaluation and simulation properties
*/
public Result fit(final Evaluation evaluation, final SimulationProperties props, final LocalDate simulationDate) {
Objects.requireNonNull(varietyParameterDao, "varietyParameterDao must be set before calling this method!");
Objects.requireNonNull(evaluation.getPhases(), "phases must not be null in Evaluation!");
Objects.requireNonNull(props.getSpecies(), "species must not be null in SimulationProperties!");
Objects.requireNonNull(props.getVariety(), "variety must not be null in SimulationProperties!");
if (evaluation.getPhases().isEmpty()) {
throw new IllegalArgumentException("Evaluation.getPhases() must not return empty map!");
}
//
// check stages are not shared between phase
// if in a phase, startdoy < 365,
// - if startdoy > simulationdate doy without offset, ignore
// else if startdoy > simulationdate doy with offset, ignore
//
final int nbOfYears = varietyParameterDao.varietyParameters(props.getSpecies(), props.getVariety()) //
.getOrDefault("nban", 1.).intValue();
final int yearOffset = nbOfYears - 1;
final Map<String, Integer> stages = props.getStages();
LOGGER.trace("DOY for stages: {}", props.getStages());
final int doyOffset = (nbOfYears - 1) * 365;
final int doy = simulationDate.getDayOfYear() + doyOffset;
LOGGER.trace("nban: {}, lastDoy: {}", nbOfYears, doy);
LOGGER.trace("Loop throught each of the {} phases", evaluation.getPhases().size());
final List<CompositeIndicator> phasesToRemove = new ArrayList<>();
for (final CompositeIndicator phase : evaluation.getPhases()) {
final String endStage = phase.getName();
final String startStage = phase.getFirstIndicator().getName();
final Integer startDoy = stages.get(startStage);
final Integer endDoy = stages.get(endStage);
LOGGER.trace("Phase {}-{} = from {} to {}", startStage, endStage, startDoy, endDoy);
if (startDoy > doy) {
LOGGER.info("Remove the phase {} because start stage > lastDate", phase.getId());
phasesToRemove.add(phase);
}
}
evaluation.getIndicators().removeAll(phasesToRemove);
if (evaluation.getPhases().isEmpty()) {
LOGGER.error("No phase to compute at {} / {}", simulationDate, doy);
return null;
}
LOGGER.trace("Set stage to lastDate if stage > doy ({})", doy);
for (final Map.Entry<String, Integer> stage : stages.entrySet()) {
if (stage.getValue() > doy) {
LOGGER.trace("Stage {} {} => {}", stage.getKey(), stage.getValue(), doy);
stage.setValue(doy);
}
}
final var newStages = stages.entrySet().stream() //
.map(e -> e.getKey().concat(": ").concat(String.valueOf(e.getValue()))) //
.collect(Collectors.joining(", "));
LOGGER.trace("Set DOY for stages: {}", newStages);
props.set(SimulationProperties.Property.STAGES, newStages);
LOGGER.trace("Set simulation period according to crop ({}) and nban ({})", props.getVariety(), nbOfYears);
props.set(SimulationProperties.Property.START_YEAR, simulationDate.getYear() - yearOffset);
props.set(SimulationProperties.Property.END_YEAR, simulationDate.getYear());
LOGGER.trace("period: {} − {}", props.getStartYear(), props.getEndYear());
// as DOY for stages are provided, unset phenological model to use agroclimatic mode
props.unset(SimulationProperties.Property.PHENOLOGICAL_MODEL);
return new Result(evaluation, props);
}
}
......@@ -17,12 +17,8 @@
package fr.agrometinfo.seasonhandler.jms;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import jakarta.jms.Destination;
import jakarta.jms.JMSConsumer;
......@@ -34,10 +30,10 @@ import jakarta.jms.MessageListener;
import org.apache.activemq.artemis.jms.client.ActiveMQMessage;
import fr.agrometinfo.seasonhandler.MainConfiguration;
import fr.agrometinfo.seasonhandler.SimulationFitter;
import fr.agrometinfo.seasonhandler.dao.AmiSimulationDao;
import fr.inrae.agroclim.indicators.exception.IndicatorsException;
import fr.inrae.agroclim.indicators.model.Evaluation;
import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
import fr.inrae.agroclim.season.core.SimulationLauncher;
import fr.inrae.agroclim.season.core.dao.ClimaticScenarioDao;
import fr.inrae.agroclim.season.core.dao.VarietyParameterDao;
......@@ -95,6 +91,11 @@ public final class SafranReceiver implements MessageListener, Runnable {
@Setter
private SimulationLauncher launcher;
/**
* Instance to fit simulation properties and evaluation according to variety and date.
*/
private final SimulationFitter fitter = new SimulationFitter();
/**
* DAO to get the number of year of crops.
*/
......@@ -147,53 +148,15 @@ public final class SafranReceiver implements MessageListener, Runnable {
*/
private void sendEvaluation(final Evaluation evaluation, final SimulationProperties props)
throws IndicatorsException {
final int nbOfYears = varietyParameterDao.varietyParameters(props.getSpecies(), props.getVariety()) //
.getOrDefault("nban", 1.).intValue();
final int doyOffset = (nbOfYears - 1) * 365;
final Map<String, Integer> stages = props.getStages();
LOGGER.trace("DOY for stages: {}", props.getStages());
final int lastDoy = lastDate.getDayOfYear() + doyOffset;
LOGGER.trace("nban: {}, lastDoy: {}", nbOfYears, lastDoy);
LOGGER.traceEntry("Loop on each phase of the evaluation");
final List<CompositeIndicator> phasesToRemove = new ArrayList<>();
for (final CompositeIndicator phase : evaluation.getPhases()) {
final String endStage = phase.getName();
final String startStage = phase.getFirstIndicator().getName();
final Integer startDoy = stages.get(startStage);
final Integer endDoy = stages.get(endStage);
LOGGER.trace("Phase {}-{} = from {} to {}", startStage, endStage, startDoy, endDoy);
if (startDoy > lastDoy) {
LOGGER.info("Remove the phase {} because start stage > lastDate", phase.getId());
phasesToRemove.add(phase);
}
}
evaluation.getIndicators().removeAll(phasesToRemove);
if (evaluation.getPhases().isEmpty()) {
LOGGER.error("No phase to compute at {}", lastDate);
return;
}
LOGGER.trace("Set stage to lastDate if stage > lastDoy ({})", lastDoy);
for (final Map.Entry<String, Integer> stage : stages.entrySet()) {
if (stage.getValue() > lastDoy) {
stage.setValue(lastDoy);
}
}
final var newStages = stages.entrySet().stream() //
.map(e -> e.getKey().concat(": ").concat(String.valueOf(e.getValue()))) //
.collect(Collectors.joining(", "));
props.set(SimulationProperties.Property.STAGES, newStages);
LOGGER.trace("New DOY for stages: {}", props.getStages());
var result = fitter.fit(evaluation, props, lastDate);
LOGGER.trace("Define the simulation period according to crop {}: nban={}", props.getVariety(), nbOfYears);
props.set(SimulationProperties.Property.START_YEAR, lastDate.getYear() + 1 - nbOfYears);
props.set(SimulationProperties.Property.END_YEAR, lastDate.getYear());
if (result == null) {
return;
}
LOGGER.info("Sending evaluation using SimulationLauncher.launch()...");
// as DOY for stages are provided, unset phenological model to use agroclimatic mode
props.unset(SimulationProperties.Property.PHENOLOGICAL_MODEL);
final Integer simulationId = launcher.launch(evaluation, props, USERNAME);
final Integer simulationId = launcher.launch(result.evaluation(), result.simulationProperties(), USERNAME);
LOGGER.info("Simulation is created: #{}", simulationId);
amiSimulationDao.create(lastDate, simulationId);
}
......
/*
* Copyright (C) 2024 INRAE AgroClim
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.agrometinfo.seasonhandler;
import fr.inrae.agroclim.indicators.model.Evaluation;
import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
import fr.inrae.agroclim.indicators.model.indicator.Indicator;
import fr.inrae.agroclim.indicators.model.indicator.Sum;
import fr.inrae.agroclim.season.core.dao.VarietyParameterDao;
import fr.inrae.agroclim.season.core.simulationproperties.SimulationProperties;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test {@link SimulationFitter}.
*
* @author Olivier Maury
*/
public class SimulationFitterTest {
/**
* Instance to test.
*/
private final SimulationFitter fitter = new SimulationFitter();
/**
* DAO to get the number of year of crops.
*/
private final VarietyParameterDao varietyParameterDao;
/**
* Constructor.
*/
public SimulationFitterTest() {
varietyParameterDao = mock();
/*
species = blé
variety = soissons
*/
when(varietyParameterDao.varietyParameters("blé", "soissons")).thenReturn(Map.of("nban", 2.));
fitter.setVarietyParameterDao(varietyParameterDao);
}
@Test
void goodWithStages() {
/*
Simulation with stages on 2 years
- s0: 274, s1: 307
- s2: 366, s8: 730
*/
final var config = new MainConfiguration(
Paths.get("src", "test", "resources", "config-good-with-stages.properties").toFile());
assertNotNull(config);
final Optional<String> init = config.init();
assertFalse(init.isPresent(), init.toString());
final int endYear = 2023;
// doy = 729 or 364
final LocalDate lastDate = LocalDate.parse(endYear + "-12-30");
for (int i = 0; i < config.getEvaluations().size(); i++) {
final SimulationProperties props = config.getSimulationProperties().get(i);
final Evaluation evaluation = config.getEvaluations().get(i);
var result = fitter.fit(evaluation, props, lastDate);
assertNotNull(result);
assertNotNull(result.evaluation());
assertNotNull(result.simulationProperties());
assertEquals(endYear - 1, result.simulationProperties().getStartYear());
assertEquals(endYear, result.simulationProperties().getEndYear());
}
}
/**
* agroclim/agrometinfo/www#105
*/
@Test
void winter() {
final String langCode = Locale.getDefault().getLanguage();
/*
winter : s1s6 XXXX-10-31 YYYY-08-31
*/
Indicator startStage = new Sum();
startStage.setName(langCode, "s1");
List<CompositeIndicator> phases = new ArrayList<>();
CompositeIndicator winter = new CompositeIndicator();
winter.setName(langCode, "s6");
winter.add(startStage);
phases.add(winter);
/*
stages = s0: 274, s1: 307, s2: 366, s3: 425, s4: 456, s5: 516, s6: 608, s7: 653, s8: 730
*/
Map<String, Integer> stages = Map.of("s1", 307, "s6", 608);
final Evaluation evaluation = mock();
when(evaluation.getPhases()).thenReturn(phases);
final SimulationProperties props = mock();
when(props.getStages()).thenReturn(stages);
when(props.getSpecies()).thenReturn("blé");
when(props.getVariety()).thenReturn("soissons");
final int endYear = 2023;
// doy = 729 or 364
final LocalDate lastDate = LocalDate.parse(endYear + "-12-30");
var result = fitter.fit(evaluation, props, lastDate);
assertNotNull(result);
assertNotNull(result.evaluation());
assertNotNull(result.simulationProperties());
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment