Nantes Université

Skip to content
Extraits de code Groupes Projets
Valider 6000dd53 rédigé par François-Xavier Lebastard's avatar François-Xavier Lebastard
Parcourir les fichiers

UNOTOPLYS-109 UNOTOPLYS-100 feat(expression)

ajout de la fonction hasAnswer,
renommage de la méthode moyenneQuestions en average
suppression de la méthode moyenne
la méthode moyenneQuestions est gardée pour la rétrocompatibilité
parent 14f7e90c
Branches
Étiquettes
1 requête de fusion!121UNAPLLYTMA-10 - EVOL 7 : Intégration d'un bouton "Imprimer"
...@@ -3,6 +3,7 @@ package com.unantes.orientactive.condition; ...@@ -3,6 +3,7 @@ package com.unantes.orientactive.condition;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
...@@ -33,10 +34,16 @@ import com.unantes.orientactive.service.dto.VariableDTO; ...@@ -33,10 +34,16 @@ import com.unantes.orientactive.service.dto.VariableDTO;
*/ */
public class Expression implements IExpression { public class Expression implements IExpression {
public static final String VARIABLE_PART_SEPARATOR = "_";
private static final Logger LOGGER = LoggerFactory.getLogger(Expression.class); private static final Logger LOGGER = LoggerFactory.getLogger(Expression.class);
/**
* Le séparateur entre les différentes parties d'une variable
*/
private static final String VARIABLE_PART_SEPARATOR = "_";
/**
* Lors du calcul des variables {@link #addVariables(List)}, une boucle infi peut se produire. Cette constante en est le seuil de détection.
*/
public static final int INFINITE_LOOP_THRESHOLD = 10000; public static final int INFINITE_LOOP_THRESHOLD = 10000;
/** /**
...@@ -71,11 +78,21 @@ public class Expression implements IExpression { ...@@ -71,11 +78,21 @@ public class Expression implements IExpression {
*/ */
private final ExpressionItemGroup<Double> scores; private final ExpressionItemGroup<Double> scores;
/**
* Contient les questions avec des réponses.
*/
private final Set<String> questionsWithAnswer;
/** /**
* Le parser d'expression SpEL. * Le parser d'expression SpEL.
*/ */
private final ExpressionParser parser; private final ExpressionParser parser;
/**
* Contient les scores associés à chaque réponse possible d'une question. La clé est calculée par {@link #getQuestionReference(String, String)}.
*/
private final Map<String, Double> answersScores;
private StandardEvaluationContext context; private StandardEvaluationContext context;
/** /**
...@@ -87,6 +104,8 @@ public class Expression implements IExpression { ...@@ -87,6 +104,8 @@ public class Expression implements IExpression {
scores = new ExpressionItemGroup(0D); scores = new ExpressionItemGroup(0D);
parser = new SpelExpressionParser(); parser = new SpelExpressionParser();
context = new StandardEvaluationContext(this); context = new StandardEvaluationContext(this);
questionsWithAnswer = new HashSet<>();
answersScores = new HashMap<>();
} }
@Override @Override
...@@ -105,8 +124,13 @@ public class Expression implements IExpression { ...@@ -105,8 +124,13 @@ public class Expression implements IExpression {
scores.put(questionRef, questionScore.map(oldScore -> score + oldScore).orElse(score)); scores.put(questionRef, questionScore.map(oldScore -> score + oldScore).orElse(score));
} }
/**
* Récupère le score d'une question. Comme le score n'est pas un élément obligatoire de la réponse, le retour est optionnel.
* @param questionRef la référence à une question {@link #getQuestionReference(String, String)}
* @return
*/
protected Optional<Double> getQuestionScore(final String questionRef) { protected Optional<Double> getQuestionScore(final String questionRef) {
if (!scores.containsKey(questionRef)) { if (!hasScore(questionRef)) {
return Optional.empty(); return Optional.empty();
} }
return Optional.of(scores.get(questionRef)); return Optional.of(scores.get(questionRef));
...@@ -142,23 +166,6 @@ public class Expression implements IExpression { ...@@ -142,23 +166,6 @@ public class Expression implements IExpression {
return scores; return scores;
} }
/**
* Permet de réaliser une moyenne. Cette méthode peut être appelée depuis une expression avec cette syntaxe :
* {@code moyenne(scores[Q1], scores[Q2])}
*
* @param operandes
* un ensemble d'entier
* @return une moyenne arithmétique
*/
public Double moyenne(Integer... operandes) {
if (ArrayUtils.isEmpty(operandes)) {
return 0D;
}
DescriptiveStatistics stats = new DescriptiveStatistics();
Stream.of(operandes).filter(Objects::nonNull).forEach(stats::addValue);
return stats.getMean();
}
/** /**
* Permet de réaliser une moyenne. Cette méthode peut être appelée depuis une expression avec cette syntaxe : * Permet de réaliser une moyenne. Cette méthode peut être appelée depuis une expression avec cette syntaxe :
* {@code moyenne(scores[Q1], scores[Q2])}. * {@code moyenne(scores[Q1], scores[Q2])}.
...@@ -169,13 +176,13 @@ public class Expression implements IExpression { ...@@ -169,13 +176,13 @@ public class Expression implements IExpression {
* un tableau de références de questions * un tableau de références de questions
* @return une moyenne arithmétique * @return une moyenne arithmétique
*/ */
public Double moyenneQuestions(String... questionsReferences) { public Double average(String... questionsReferences) {
if (ArrayUtils.isEmpty(questionsReferences)) { if (ArrayUtils.isEmpty(questionsReferences)) {
return 0D; return 0D;
} }
//@formatter:off //@formatter:off
final List<Double> questionsScores = Stream.of(questionsReferences) final List<Double> questionsScores = Stream.of(questionsReferences)
.filter(this::isQuestionAnswerExists) .filter(this::hasScore)
.map(this.scores::get) .map(this.scores::get)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toList()); .collect(Collectors.toList());
...@@ -188,10 +195,39 @@ public class Expression implements IExpression { ...@@ -188,10 +195,39 @@ public class Expression implements IExpression {
return stats.getMean(); return stats.getMean();
} }
protected boolean isQuestionAnswerExists(String questionReference) { /**
* Permet de réaliser une moyenne. Cette méthode peut être appelée depuis une expression avec cette syntaxe :
* {@code moyenne(scores[Q1], scores[Q2])}.
* Ne sont compatbilisées que les questions ayant une réponse dans {@link #answers} et ayant un score (même égal à zéro).
* Les autres questions ne sont pas compatbilisées dans le calcul du score.
* @deprecated : utiliser {@link #average(String...)} en lieu et place
* @param questionsReferences
* un tableau de références de questions
* @return une moyenne arithmétique
*/
@Deprecated(forRemoval = true, since = "0.1.1")
public Double moyenneQuestions(String... questionsReferences) {
return average(questionsReferences);
}
/**
* Est ce qu'un score existe pour cette question.
* @param questionReference
* @return
*/
public boolean hasScore(String questionReference) {
return scores.keySet().contains(questionReference); return scores.keySet().contains(questionReference);
} }
/**
* Est ce qu'une réponse existe pour cette question.
* @param questionReference
* @return
*/
public boolean hasAnswer(String questionReference) {
return questionsWithAnswer.contains(questionReference);
}
/** /**
* Ou exclusif. * Ou exclusif.
* @param op1 opérande 1 * @param op1 opérande 1
...@@ -208,7 +244,6 @@ public class Expression implements IExpression { ...@@ -208,7 +244,6 @@ public class Expression implements IExpression {
return parser.parseExpression(expression).getValue(context, classz); return parser.parseExpression(expression).getValue(context, classz);
} catch (ParseException e) { } catch (ParseException e) {
LOGGER.error("Erreur à l'analyse syntaxique de l'expression {}", expression, e); LOGGER.error("Erreur à l'analyse syntaxique de l'expression {}", expression, e);
} catch (ExpressionException e) { } catch (ExpressionException e) {
LOGGER.error("Erreur à l'évaluation de l'expression {}", expression, e); LOGGER.error("Erreur à l'évaluation de l'expression {}", expression, e);
LOGGER.info("Variables : {}", variables); LOGGER.info("Variables : {}", variables);
...@@ -227,8 +262,6 @@ public class Expression implements IExpression { ...@@ -227,8 +262,6 @@ public class Expression implements IExpression {
} }
protected void addAnswer(final AnswerDTO answer) { protected void addAnswer(final AnswerDTO answer) {
// contient les scores associés à chaque réponses
final Map<String, Double> answersScores = new HashMap<>();
// on met par défaut toutes les réponses comme non sélectionnées // on met par défaut toutes les réponses comme non sélectionnées
//@formatter:off //@formatter:off
final List<MultipleChoiceItem> screenQuestions = final List<MultipleChoiceItem> screenQuestions =
...@@ -239,30 +272,24 @@ public class Expression implements IExpression { ...@@ -239,30 +272,24 @@ public class Expression implements IExpression {
//@formatter:on //@formatter:on
final String screenReference = answer.getScreenReference(); final String screenReference = answer.getScreenReference();
for (final MultipleChoiceItem screenItem : screenQuestions) { for (final MultipleChoiceItem screenItem : screenQuestions) {
answersScores.putAll(initializeScreenAnswers(screenReference, screenItem.getReference(), screenItem.getChoices())); initializeScreenAnswers(screenReference, screenItem.getReference(), screenItem.getChoices());
} }
// puis on vient sélectionner celles qui ont étés selectionnées // puis on vient sélectionner celles qui ont étés selectionnées
// récupération des réponses de chaque question de l'écran // récupération des réponses de chaque question de l'écran
// on peuple la table des scores en fonction de la répnse sélectionnée // on peuple la table des scores en fonction de la répnse sélectionnée
final List<AnswerElements> answerElements = answer.getAnswerElements(); final List<AnswerElements> answerElements = answer.getAnswerElements();
for (final AnswerElements answerElement : answerElements) { for (final AnswerElements answerElement : answerElements) {
registerAnswers(answersScores, screenReference, answerElement); registerAnswers(screenReference, answerElement);
} }
} }
/** /**
* Initialisation des variables relatives à un écran : * Initialisation des variables relatives à un écran : toutes les réponses sont non sélectionnées. {@link #registerAnswers(String, AnswerElements) viendra ensuite sélectionnées les réponses effectivement sélectionnées}</li>
* <ul>
* <li>Toutes les réponses sont non sélectionnées. {@link #registerAnswers(Map, String, AnswerElements) viendra ensuite sélectionnées les réponses effectivement sélectionnées}</li>
* <li>On retourne les scores associés à chaque réponse. Cette Map sera utilisée par #registerAnswers(Map, String, AnswerElements) pour peupler {@link #scores}</li>
* </ul>
* @param screenReference une réference d'écran * @param screenReference une réference d'écran
* @param questionReference une référence de question * @param questionReference une référence de question
* @param questionChoices les réponses possibles à la question * @param questionChoices les réponses possibles à la question
* @return les scores de l'écran courant
*/ */
private Map<String, Double> initializeScreenAnswers(final String screenReference, final String questionReference, final List<Choice> questionChoices) { private void initializeScreenAnswers(final String screenReference, final String questionReference, final List<Choice> questionChoices) {
final Map<String, Double> answersScores = new HashMap<>();
for (final Choice choice : questionChoices) { for (final Choice choice : questionChoices) {
final String answerRef = getAnswerReference(screenReference, questionReference, choice.getValue()); final String answerRef = getAnswerReference(screenReference, questionReference, choice.getValue());
// on stocke le score associé à chaque réponse; utilisé pour calculer le score de la question en fonction de la réponse sélectionnée // on stocke le score associé à chaque réponse; utilisé pour calculer le score de la question en fonction de la réponse sélectionnée
...@@ -273,32 +300,64 @@ public class Expression implements IExpression { ...@@ -273,32 +300,64 @@ public class Expression implements IExpression {
// dans un premier temps, on considère toutes les réponses comme non sélectionnées. // dans un premier temps, on considère toutes les réponses comme non sélectionnées.
addAnswer(answerRef, false); addAnswer(answerRef, false);
} }
return answersScores;
} }
private void registerAnswers(final Map<String, Double> answersScores, final String screenReference, final AnswerElements answerElement) { /**
* Enregistre les réponses d'une question. Peuple {@link #answers}, {@link #questionsWithAnswer}, {@link #scores}.
* @param screenReference la référence de l'écran courant
* @param answerElement les réponses d'une question
*/
private void registerAnswers(final String screenReference, final AnswerElements answerElement) {
List<String> answer = answerElement.getAnswer(); List<String> answer = answerElement.getAnswer();
if (CollectionUtils.isNotEmpty(answer)) { if (CollectionUtils.isNotEmpty(answer)) {
String questionReference = answerElement.getQuestionReference(); String questionReference = answerElement.getQuestionReference();
for (String value : answer) { for (String value : answer) {
String questionRef = getQuestionReference(screenReference, questionReference); registerAnswer(screenReference, questionReference, value);
String answerRef = getAnswerReference(screenReference, questionReference, value);
if (answersScores.containsKey(answerRef)) {
addQuestionScore(questionRef, answersScores.get(answerRef));
}
addAnswer(answerRef, true);
} }
} }
} }
/**
* Enregistre une réponse à une question
* @param screenReference la référence de l'écran
* @param questionReference la référence d'une question
* @param value la réponse
*/
private void registerAnswer(final String screenReference, final String questionReference, final String value) {
String questionRef = getQuestionReference(screenReference, questionReference);
String answerRef = getAnswerReference(screenReference, questionReference, value);
if (answersScores.containsKey(answerRef)) {
addQuestionScore(questionRef, answersScores.get(answerRef));
}
addAnswer(answerRef, true);
questionsWithAnswer.add(questionRef);
}
/**
* Construit la référence à une réponse : referenceEcran_referenceQuestion_valeurReponse
* @param screenReference reference de l'écran contenant la question
* @param questionReference reference d'une question de l'écran
* @param value reference d'une réponse de la question
* @return
*/
protected String getAnswerReference(final String screenReference, final String questionReference, final String value) { protected String getAnswerReference(final String screenReference, final String questionReference, final String value) {
return screenReference + VARIABLE_PART_SEPARATOR + questionReference + VARIABLE_PART_SEPARATOR + value; return getQuestionReference(screenReference, questionReference) + VARIABLE_PART_SEPARATOR + value;
} }
/**
* Construit la référence d'une question : referenceEcran_referenceQuestion
* @param screenReference reference de l'écran contenant la question
* @param questionReference reference d'une question de l'écran
* @return
*/
protected String getQuestionReference(final String screenReference, final String questionReference) { protected String getQuestionReference(final String screenReference, final String questionReference) {
return screenReference + VARIABLE_PART_SEPARATOR + questionReference; return screenReference + VARIABLE_PART_SEPARATOR + questionReference;
} }
/**
* Evalue une variable. Ne doit être appelée que si les variables dont dépend la variable passée en paramètre ont été évaluées.
* @param variable
*/
private void evaluate(VariableDTO variable) { private void evaluate(VariableDTO variable) {
try { try {
final Double variableValue = evaluate(variable.getExpression(), Double.class); final Double variableValue = evaluate(variable.getExpression(), Double.class);
...@@ -343,6 +402,11 @@ public class Expression implements IExpression { ...@@ -343,6 +402,11 @@ public class Expression implements IExpression {
} }
} }
/**
* Détermine si les variables précédentes de la variable passée en paramètres ont toutes été calculées
* @param variable une variable de l'expression
* @return
*/
private boolean allPreviousVariablesAreComputed(VariableDTO variable) { private boolean allPreviousVariablesAreComputed(VariableDTO variable) {
final List<String> previousVariables = variable.getPreviousVariables().stream().map(VariableDTO::getReference).collect(Collectors.toList()); final List<String> previousVariables = variable.getPreviousVariables().stream().map(VariableDTO::getReference).collect(Collectors.toList());
final Set<String> computedVariablesReferences = variables.keySet(); final Set<String> computedVariablesReferences = variables.keySet();
......
...@@ -26,58 +26,35 @@ class ExpressionTest extends AbstractTest { ...@@ -26,58 +26,35 @@ class ExpressionTest extends AbstractTest {
ExpressionTest() {serviceConverter = new ServiceConverter();} ExpressionTest() {serviceConverter = new ServiceConverter();}
@Test
void moyenneNoInput() {
final Expression expression = new Expression();
assertEquals(0d, expression.moyenne());
}
@Test
void moyenneNullInput() {
final Expression expression = new Expression();
assertEquals(0d, expression.moyenne(null));
}
@Test
void moyenneNullAndValidInput() {
final Expression expression = new Expression();
assertEquals(12, expression.moyenne(null, 10, 14));
}
@Test
void moyenneValidInput() {
final Expression expression = new Expression();
assertEquals(12, expression.moyenne(10, 14));
}
@Test @Test
void moyenQuestionAvecQuestion(){ void averageAvecQuestion(){
final Expression expression = new Expression(); final Expression expression = new Expression();
expression.addAnswer("Q1_R1", true); expression.addAnswer("Q1_R1", true);
expression.addQuestionScore("Q1_R1", 10D); expression.addQuestionScore("Q1_R1", 10D);
expression.addAnswer("Q2_R2", true); expression.addAnswer("Q2_R2", true);
expression.addQuestionScore("Q2_R2", 20D); expression.addQuestionScore("Q2_R2", 20D);
assertEquals(15D, expression.moyenneQuestions("Q1_R1", "Q2_R2")); assertEquals(15D, expression.average("Q1_R1", "Q2_R2"));
} }
@Test @Test
void moyenQuestionAvecQuestionNonRepondue() { void averageAvecQuestionNonRepondue() {
final Expression expression = new Expression(); final Expression expression = new Expression();
expression.addAnswer("Q1_R1", true); expression.addAnswer("Q1_R1", true);
expression.addQuestionScore("Q1_R1", 10D); expression.addQuestionScore("Q1_R1", 10D);
assertEquals(10D, expression.moyenneQuestions("Q1_R1", "Q1_R2")); assertEquals(10D, expression.average("Q1_R1", "Q1_R2"));
} }
@Test @Test
void moyenQuestionSansQuestions() { void averageSansQuestions() {
final Expression expression = new Expression(); final Expression expression = new Expression();
assertEquals(0D, expression.moyenneQuestions()); assertEquals(0D, expression.average());
} }
@Test @Test
void moyenQuestionAvecQuestionSansScore() { void averageAvecQuestionSansScore() {
final Expression expression = new Expression(); final Expression expression = new Expression();
expression.addAnswer("Q1_R1", false); expression.addAnswer("Q1_R1", false);
assertEquals(0D, expression.moyenneQuestions("Q1_R1")); assertEquals(0D, expression.average("Q1_R1"));
} }
@Test @Test
void evaluate() { void evaluate() {
...@@ -102,10 +79,10 @@ class ExpressionTest extends AbstractTest { ...@@ -102,10 +79,10 @@ class ExpressionTest extends AbstractTest {
expression.addQuestionScore("Q3", 11d); expression.addQuestionScore("Q3", 11d);
expression.addVariable("moy_mat_litt_bacG", 20d); expression.addVariable("moy_mat_litt_bacG", 20d);
expression.addVariable("moy_spe_scient", 5d); expression.addVariable("moy_spe_scient", 5d);
assertEquals(15.5d, expression.evaluate("moyenne(scores[Q1], scores[Q2])", Double.class)); assertEquals(15.5d, expression.evaluate("average('Q1', 'Q2')", Double.class));
assertEquals(15.5d, expression.evaluate("0.7 * variables[moy_mat_litt_bacG] + 0.3 * variables[moy_spe_scient]", Double.class)); assertEquals(15.5d, expression.evaluate("0.7 * variables[moy_mat_litt_bacG] + 0.3 * variables[moy_spe_scient]", Double.class));
assertEquals(14d, expression.evaluate("answers[Q1_R4] ? moyenne(scores[Q1], scores[Q2]) : moyenne(scores[Q1], scores[Q2], scores[Q3])", Double.class)); assertEquals(14d, expression.evaluate("answers[Q1_R4] ? average('Q1', 'Q2') : average('Q1', 'Q2', 'Q3')", Double.class));
assertEquals(15.5d, expression.evaluate("answers[Q1_R3] ? moyenne(scores[Q1], scores[Q2]) : moyenne(scores[Q1], scores[Q2], scores[Q3])", Double.class)); assertEquals(15.5d, expression.evaluate("answers[Q1_R3] ? average('Q1', 'Q2') : average('Q1', 'Q2', 'Q3')", Double.class));
assertFalse(expression.evaluate("(answers[Q1_R4] || answers[Q2_R4]) && answers[Q3_R4]", Boolean.class)); assertFalse(expression.evaluate("(answers[Q1_R4] || answers[Q2_R4]) && answers[Q3_R4]", Boolean.class));
assertTrue(expression.evaluate("answers[Q2_R1] && variables[moy_spe_scient] >= 0 && variables[moy_spe_scient] < 6", Boolean.class)); assertTrue(expression.evaluate("answers[Q2_R1] && variables[moy_spe_scient] >= 0 && variables[moy_spe_scient] < 6", Boolean.class));
} }
......
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Veuillez vous inscrire ou vous pour commenter