From d83edb14db71351b33ce13d09b9ad40ee449f6ec Mon Sep 17 00:00:00 2001
From: Jan Tymel <410388@mail.muni.cz>
Date: Tue, 23 Feb 2021 21:35:14 +0100
Subject: [PATCH] First iteration of questionnaire answers evaluation

---
 .../QuestionnaireEvaluationController.java    |  58 +++++++++
 .../adaptive/domain/QuestionAnswer.java       |  32 +++++
 .../adaptive/domain/QuestionAnswerId.java     |  49 +++++++
 .../adaptive/domain/QuestionPhaseResult.java  |  61 +++++++++
 .../adaptive/dto/run/QuestionAnswerDTO.java   |  31 +++++
 .../dto/run/QuestionnairePhaseAnswersDTO.java |  21 +++
 .../facade/QuestionnaireEvaluationFacade.java |  25 ++++
 .../repository/QuestionAnswerRepository.java  |  10 ++
 .../QuestionPhaseRelationRepository.java      |   8 ++
 .../QuestionPhaseResultRepository.java        |   9 ++
 .../QuestionnaireEvaluationService.java       | 121 ++++++++++++++++++
 11 files changed, 425 insertions(+)
 create mode 100644 src/main/java/cz/muni/ics/kypo/training/adaptive/controller/QuestionnaireEvaluationController.java
 create mode 100644 src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswer.java
 create mode 100644 src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswerId.java
 create mode 100644 src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionPhaseResult.java
 create mode 100644 src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionAnswerDTO.java
 create mode 100644 src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionnairePhaseAnswersDTO.java
 create mode 100644 src/main/java/cz/muni/ics/kypo/training/adaptive/facade/QuestionnaireEvaluationFacade.java
 create mode 100644 src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java
 create mode 100644 src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java
 create mode 100644 src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java

diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/QuestionnaireEvaluationController.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/QuestionnaireEvaluationController.java
new file mode 100644
index 00000000..af9ae397
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/QuestionnaireEvaluationController.java
@@ -0,0 +1,58 @@
+package cz.muni.ics.kypo.training.adaptive.controller;
+
+import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO;
+import cz.muni.ics.kypo.training.adaptive.facade.QuestionnaireEvaluationFacade;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import io.swagger.annotations.Authorization;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+
+@RestController
+@RequestMapping(value = "/training-runs", produces = MediaType.APPLICATION_JSON_VALUE)
+@CrossOrigin(origins = "*", allowCredentials = "true", allowedHeaders = "*",
+        methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT})
+@Api(value = "/training-runs",
+        tags = "Training runs",
+        consumes = MediaType.APPLICATION_JSON_VALUE,
+        authorizations = @Authorization(value = "bearerAuth"))
+public class QuestionnaireEvaluationController {
+
+    private final QuestionnaireEvaluationFacade questionnaireEvaluationFacade;
+
+    @Autowired
+    public QuestionnaireEvaluationController(QuestionnaireEvaluationFacade questionnaireEvaluationFacade) {
+        this.questionnaireEvaluationFacade = questionnaireEvaluationFacade;
+    }
+
+    @ApiOperation(httpMethod = "POST",
+            value = "Evaluate answers to a questionnaire phase",
+            nickname = "evaluateAnswersToQuestionnaire",
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    @ApiResponses(value = {
+            @ApiResponse(code = 200, message = "Answers evaluated"),
+            @ApiResponse(code = 500, message = "Unexpected application error")
+    })
+    @PutMapping(value = "/{runId}/questionnaire-evaluation", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<Void> evaluateAnswersToQuestionnaire(@ApiParam(value = "Training run ID", required = true)
+                                                              @PathVariable("runId") Long runId,
+                                                              @ApiParam(value = "Responses to questionnaire", required = true)
+                                                              @Valid @RequestBody QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) {
+        questionnaireEvaluationFacade.evaluateAnswersToQuestionnaire(runId, questionnairePhaseAnswersDTO);
+        return ResponseEntity.noContent().build();
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswer.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswer.java
new file mode 100644
index 00000000..211b904f
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswer.java
@@ -0,0 +1,32 @@
+package cz.muni.ics.kypo.training.adaptive.domain;
+
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+@Entity
+@Table(name = "question_answer")
+public class QuestionAnswer implements Serializable {
+
+    @EmbeddedId
+    private QuestionAnswerId questionAnswerId;
+
+    private String answer;
+
+    public QuestionAnswerId getQuestionAnswerId() {
+        return questionAnswerId;
+    }
+
+    public void setQuestionAnswerId(QuestionAnswerId questionAnswerId) {
+        this.questionAnswerId = questionAnswerId;
+    }
+
+    public String getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(String answer) {
+        this.answer = answer;
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswerId.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswerId.java
new file mode 100644
index 00000000..d59dff5c
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswerId.java
@@ -0,0 +1,49 @@
+package cz.muni.ics.kypo.training.adaptive.domain;
+
+import javax.persistence.Embeddable;
+import java.io.Serializable;
+import java.util.Objects;
+
+@Embeddable
+public class QuestionAnswerId implements Serializable {
+    private Question question;
+    private Long trainingRunId;
+
+    public QuestionAnswerId() {
+    }
+
+    public QuestionAnswerId(Question question, Long trainingRunId) {
+        this.question = question;
+        this.trainingRunId = trainingRunId;
+    }
+
+    public Question getQuestion() {
+        return question;
+    }
+
+    public void setQuestion(Question question) {
+        this.question = question;
+    }
+
+    public Long getTrainingRunId() {
+        return trainingRunId;
+    }
+
+    public void setTrainingRunId(Long trainingRunId) {
+        this.trainingRunId = trainingRunId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        QuestionAnswerId that = (QuestionAnswerId) o;
+        return Objects.equals(question, that.question) &&
+                Objects.equals(trainingRunId, that.trainingRunId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(question, trainingRunId);
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionPhaseResult.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionPhaseResult.java
new file mode 100644
index 00000000..cfc2bf0d
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionPhaseResult.java
@@ -0,0 +1,61 @@
+package cz.muni.ics.kypo.training.adaptive.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+@Entity
+@Table(name = "question_phase_result")
+public class QuestionPhaseResult implements Serializable {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "questionPhaseResultGenerator")
+    @SequenceGenerator(name = "questionPhaseResultGenerator", sequenceName = "question_phase_result_seq")
+    @Column(name = "question_phase_result_id", nullable = false, unique = true)
+    private Long id;
+
+    private Long trainingRunId;
+    private int achievedResult;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    private QuestionPhaseRelation questionPhaseRelation;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getTrainingRunId() {
+        return trainingRunId;
+    }
+
+    public void setTrainingRunId(Long trainingRunId) {
+        this.trainingRunId = trainingRunId;
+    }
+
+    public int getAchievedResult() {
+        return achievedResult;
+    }
+
+    public void setAchievedResult(int achievedResult) {
+        this.achievedResult = achievedResult;
+    }
+
+    public QuestionPhaseRelation getQuestionPhaseRelation() {
+        return questionPhaseRelation;
+    }
+
+    public void setQuestionPhaseRelation(QuestionPhaseRelation questionPhaseRelation) {
+        this.questionPhaseRelation = questionPhaseRelation;
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionAnswerDTO.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionAnswerDTO.java
new file mode 100644
index 00000000..772e2fef
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionAnswerDTO.java
@@ -0,0 +1,31 @@
+package cz.muni.ics.kypo.training.adaptive.dto.run;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.constraints.NotNull;
+
+public class QuestionAnswerDTO {
+
+    @ApiModelProperty(value = "ID of answered question", example = "1")
+    @NotNull(message = "ID of the answered question must not be null")
+    private Long questionId;
+
+    @ApiModelProperty(value = "Answer to the question", example = "An answer")
+    private String answer;
+
+    public Long getQuestionId() {
+        return questionId;
+    }
+
+    public void setQuestionId(Long questionId) {
+        this.questionId = questionId;
+    }
+
+    public String getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(String answer) {
+        this.answer = answer;
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionnairePhaseAnswersDTO.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionnairePhaseAnswersDTO.java
new file mode 100644
index 00000000..9a4eecd6
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionnairePhaseAnswersDTO.java
@@ -0,0 +1,21 @@
+package cz.muni.ics.kypo.training.adaptive.dto.run;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.Valid;
+import java.util.List;
+
+public class QuestionnairePhaseAnswersDTO {
+
+    @Valid
+    @ApiModelProperty(value = "Answers to the questionnaire provided by user", required = true)
+    private List<QuestionAnswerDTO> answers;
+
+    public List<QuestionAnswerDTO> getAnswers() {
+        return answers;
+    }
+
+    public void setAnswers(List<QuestionAnswerDTO> answers) {
+        this.answers = answers;
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/QuestionnaireEvaluationFacade.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/QuestionnaireEvaluationFacade.java
new file mode 100644
index 00000000..fd657740
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/QuestionnaireEvaluationFacade.java
@@ -0,0 +1,25 @@
+package cz.muni.ics.kypo.training.adaptive.facade;
+
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswer;
+import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO;
+import cz.muni.ics.kypo.training.adaptive.service.QuestionnaireEvaluationService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class QuestionnaireEvaluationFacade {
+
+    private final QuestionnaireEvaluationService questionnaireEvaluationService;
+
+    @Autowired
+    public QuestionnaireEvaluationFacade(QuestionnaireEvaluationService questionnaireEvaluationService) {
+        this.questionnaireEvaluationService = questionnaireEvaluationService;
+    }
+
+    public void evaluateAnswersToQuestionnaire(Long runId, QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) {
+        List<QuestionAnswer> savedAnswers = questionnaireEvaluationService.saveAnswersToQuestionnaire(runId, questionnairePhaseAnswersDTO);
+        questionnaireEvaluationService.evaluateAnswersToQuestionnaire(runId, savedAnswers);
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java
new file mode 100644
index 00000000..2c5e54e8
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java
@@ -0,0 +1,10 @@
+package cz.muni.ics.kypo.training.adaptive.repository;
+
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswer;
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswerId;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface QuestionAnswerRepository extends JpaRepository<QuestionAnswer, QuestionAnswerId> {
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseRelationRepository.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseRelationRepository.java
index a79d1229..fac903e2 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseRelationRepository.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseRelationRepository.java
@@ -2,8 +2,16 @@ package cz.muni.ics.kypo.training.adaptive.repository;
 
 import cz.muni.ics.kypo.training.adaptive.domain.QuestionPhaseRelation;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 
+import java.util.List;
+import java.util.Set;
+
 @Repository
 public interface QuestionPhaseRelationRepository extends JpaRepository<QuestionPhaseRelation, Long> {
+
+    @Query("SELECT r FROM QuestionPhaseRelation r INNER JOIN r.questions q WHERE q.id IN :questionIdList")
+    List<QuestionPhaseRelation> findAllByQuestionIdList(@Param("questionIdList") Set<Long> questionIdList);
 }
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java
new file mode 100644
index 00000000..86c9945d
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java
@@ -0,0 +1,9 @@
+package cz.muni.ics.kypo.training.adaptive.repository;
+
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionPhaseResult;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface QuestionPhaseResultRepository extends JpaRepository<QuestionPhaseResult, Long> {
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java
new file mode 100644
index 00000000..c1d70ef7
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java
@@ -0,0 +1,121 @@
+package cz.muni.ics.kypo.training.adaptive.service;
+
+import cz.muni.ics.kypo.training.adaptive.domain.Question;
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswer;
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswerId;
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionChoice;
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionPhaseRelation;
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionPhaseResult;
+import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionAnswerDTO;
+import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO;
+import cz.muni.ics.kypo.training.adaptive.repository.QuestionAnswerRepository;
+import cz.muni.ics.kypo.training.adaptive.repository.QuestionPhaseRelationRepository;
+import cz.muni.ics.kypo.training.adaptive.repository.QuestionPhaseResultRepository;
+import cz.muni.ics.kypo.training.adaptive.repository.QuestionRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Service
+@Transactional
+public class QuestionnaireEvaluationService {
+
+    private static final Logger LOG = LoggerFactory.getLogger(QuestionnaireEvaluationService.class);
+
+    private final QuestionRepository questionRepository;
+    private final QuestionAnswerRepository questionAnswerRepository;
+    private final QuestionPhaseRelationRepository questionPhaseRelationRepository;
+    private final QuestionPhaseResultRepository questionPhaseResultRepository;
+
+    @Autowired
+    public QuestionnaireEvaluationService(QuestionRepository questionRepository, QuestionAnswerRepository questionAnswerRepository,
+                                          QuestionPhaseRelationRepository questionPhaseRelationRepository, QuestionPhaseResultRepository questionPhaseResultRepository) {
+        this.questionRepository = questionRepository;
+        this.questionAnswerRepository = questionAnswerRepository;
+        this.questionPhaseRelationRepository = questionPhaseRelationRepository;
+        this.questionPhaseResultRepository = questionPhaseResultRepository;
+    }
+
+    public List<QuestionAnswer> saveAnswersToQuestionnaire(Long runId, QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) {
+        List<QuestionAnswer> result = new ArrayList<>();
+        for (QuestionAnswerDTO answer : questionnairePhaseAnswersDTO.getAnswers()) {
+            Question question = questionRepository.findById(answer.getQuestionId())
+                    .orElseThrow(() -> new RuntimeException("Question was not found"));
+            // TODO throw proper exception once kypo2-training is migrated
+
+            QuestionAnswer questionAnswer = new QuestionAnswer();
+            questionAnswer.setQuestionAnswerId(new QuestionAnswerId(question, runId));
+            questionAnswer.setAnswer(answer.getAnswer());
+
+            questionAnswerRepository.save(questionAnswer);
+            result.add(questionAnswer);
+        }
+
+        return result;
+    }
+
+    public void evaluateAnswersToQuestionnaire(Long runId, List<QuestionAnswer> answers) {
+        if (CollectionUtils.isEmpty(answers)) {
+            return;
+        }
+
+        Set<Long> questionIdList = answers.stream()
+                .map(QuestionAnswer::getQuestionAnswerId)
+                .map(QuestionAnswerId::getQuestion)
+                .map(Question::getId)
+                .collect(Collectors.toSet());
+
+        List<QuestionPhaseRelation> questionPhaseRelations = questionPhaseRelationRepository.findAllByQuestionIdList(questionIdList);
+        for (QuestionPhaseRelation questionPhaseRelation : questionPhaseRelations) {
+            int numberOfCorrectAnswers = 0;
+            if (CollectionUtils.isEmpty(questionPhaseRelation.getQuestions())) {
+                LOG.warn("No questions found for question phase relation {}", questionPhaseRelation.getId());
+                continue;
+            }
+
+            for (Question question : questionPhaseRelation.getQuestions()) {
+                Optional<QuestionAnswer> correspondingAnswer = findCorrespondingAnswer(question.getId(), answers);
+                if (correspondingAnswer.isPresent()) {
+                    String answer = correspondingAnswer.get().getAnswer();
+                    Optional<QuestionChoice> correspondingQuestionChoice = findCorrespondingQuestionChoice(answer, question.getChoices());
+                    if (correspondingQuestionChoice.isPresent() && correspondingQuestionChoice.get().isCorrect()) {
+                        numberOfCorrectAnswers++;
+                    }
+                } else {
+                    LOG.debug("No answer found for question {}. It is assumed as a wrong answer", question.getId());
+                }
+            }
+
+            int achievedResult = numberOfCorrectAnswers * 100 / questionPhaseRelation.getQuestions().size();
+
+            QuestionPhaseResult questionPhaseResult = new QuestionPhaseResult();
+            questionPhaseResult.setAchievedResult(achievedResult);
+            questionPhaseResult.setQuestionPhaseRelation(questionPhaseRelation);
+            questionPhaseResult.setTrainingRunId(runId);
+
+            questionPhaseResultRepository.save(questionPhaseResult);
+        }
+    }
+
+    private Optional<QuestionAnswer> findCorrespondingAnswer(Long questionId, List<QuestionAnswer> answers) {
+        return answers.stream()
+                .filter(a -> Objects.equals(questionId, a.getQuestionAnswerId().getQuestion().getId()))
+                .findFirst();
+    }
+
+    private Optional<QuestionChoice> findCorrespondingQuestionChoice(String answer, List<QuestionChoice> questionChoices) {
+        return questionChoices.stream()
+                .filter(q -> Objects.equals(answer, q.getText()))
+                .findFirst();
+    }
+}
-- 
GitLab