Index: lams_common/src/java/org/lamsfoundation/lams/qb/dao/hibernate/QbDAO.java =================================================================== diff -u -r8ba5fc977a902ebf0d18050ad8defcbd96be9548 -r6b45c9ecb6999e184ca671297026bc6fa6faf9b5 --- lams_common/src/java/org/lamsfoundation/lams/qb/dao/hibernate/QbDAO.java (.../QbDAO.java) (revision 8ba5fc977a902ebf0d18050ad8defcbd96be9548) +++ lams_common/src/java/org/lamsfoundation/lams/qb/dao/hibernate/QbDAO.java (.../QbDAO.java) (revision 6b45c9ecb6999e184ca671297026bc6fa6faf9b5) @@ -70,17 +70,23 @@ + "WHERE act.activity_id = :activityId AND tq.qb_question_uid = :qbQuestionUid GROUP BY user_id " + "HAVING opt IS NOT NULL AND user_id IS NOT NULL"; - private static final String FIND_OPTION_ANSWER_COUNT_BY_QUESTION = "SELECT tq.qb_question_uid, o.uid AS qb_option_uid, o.max_mark = 1 AS is_correct, " - + "SUM(IF(aa.uid IS NULL AND sa.uid IS NULL, 0, 1)) AS chosen_count " - + "FROM lams_qb_tool_question AS tq JOIN lams_qb_option AS o USING (qb_question_uid) " - + "LEFT JOIN lams_qb_tool_answer AS a ON a.tool_question_uid = tq.tool_question_uid AND (a.qb_option_uid IS NULL OR a.qb_option_uid = o.uid) " + private static final String FIND_OPTION_ANSWER_COUNT_BY_QUESTION = "SELECT tq.qb_question_uid, " + + "IFNULL(a3.question_answer_count, 0) AS question_answer_count, o.uid AS qb_option_uid, o.max_mark = 1 AS is_correct, " + + "SUM(IF(aa.uid IS NULL AND sa.uid IS NULL AND NOT (q.type = " + QbQuestion.TYPE_VERY_SHORT_ANSWERS + + " AND a.qb_option_uid IS NOT NULL AND o.max_mark = 1), 0, 1)) AS option_answer_count " + + "FROM lams_qb_tool_question AS tq JOIN lams_qb_question AS q ON tq.qb_question_uid = q.uid " + + "JOIN lams_qb_option AS o USING (qb_question_uid) " + + "LEFT JOIN lams_qb_tool_answer AS a ON a.tool_question_uid = tq.tool_question_uid AND (a.qb_option_uid = o.uid OR q.type <> " + + QbQuestion.TYPE_VERY_SHORT_ANSWERS + ") " + "LEFT JOIN tl_laasse10_question_result AS aq ON a.answer_uid = aq.uid " + "LEFT JOIN tl_laasse10_option_answer AS aa ON aa.question_result_uid = aq.uid AND aa.question_option_uid = o.uid AND aa.answer_boolean = 1 " + "LEFT JOIN tl_laasse10_assessment_result AS ar ON aq.result_uid = ar.uid AND ar.latest = 1 " + "LEFT JOIN tl_lascrt11_answer_log AS sa ON a.answer_uid = sa.uid AND a.answer_uid = " - + " (SELECT sa2.uid FROM tl_lascrt11_answer_log AS sa2 JOIN lams_qb_tool_answer AS a2 " - + " ON a2.answer_uid = sa2.uid AND a2.tool_question_uid = a.tool_question_uid AND sa2.session_id = sa.session_id " + + "(SELECT sa2.uid FROM tl_lascrt11_answer_log AS sa2 JOIN lams_qb_tool_answer AS a2 " + + " ON a2.answer_uid = sa2.uid AND a2.tool_question_uid = a.tool_question_uid AND sa2.session_id = sa.session_id " + " ORDER BY sa2.access_date, sa2.uid LIMIT 1) " + + "LEFT JOIN (SELECT tool_question_uid, COUNT(*) AS question_answer_count FROM lams_qb_tool_answer GROUP BY tool_question_uid) AS a3 " + + " ON tq.tool_question_uid = a3.tool_question_uid " + "WHERE tq.tool_content_id = :toolContentId GROUP BY o.uid ORDER BY tq.display_order, o.display_order"; private static final String FIND_BURNING_QUESTIONS = "SELECT b.question, COUNT(bl.uid) FROM ScratchieBurningQuestion b LEFT OUTER JOIN " @@ -432,21 +438,24 @@ .setParameter("toolContentId", toolContentId).list(); List dtos = new LinkedList<>(); Map> questionsToOptionsMap = new LinkedHashMap<>(); + Map questionsToQuestionCountMap = new HashMap<>(); // it contains all UID of correct answers Set correctOptionUids = new HashSet<>(); // build a map of question ID -> option ID -> answer count for (Object[] answerEntry : result) { Long qbQuestionUid = ((Number) answerEntry[0]).longValue(); + Map answersForOptions = questionsToOptionsMap.get(qbQuestionUid); if (answersForOptions == null) { answersForOptions = new LinkedHashMap<>(); questionsToOptionsMap.put(qbQuestionUid, answersForOptions); + questionsToQuestionCountMap.put(qbQuestionUid, ((Number) answerEntry[1]).intValue()); } - Long qbOptionUid = ((Number) answerEntry[1]).longValue(); - answersForOptions.put(qbOptionUid, ((Number) answerEntry[3]).intValue()); + Long qbOptionUid = ((Number) answerEntry[2]).longValue(); + answersForOptions.put(qbOptionUid, ((Number) answerEntry[4]).intValue()); - boolean isCorrect = ((Number) answerEntry[2]).intValue() == 1; + boolean isCorrect = ((Number) answerEntry[3]).intValue() == 1; if (isCorrect) { correctOptionUids.add(qbOptionUid); } @@ -455,8 +464,9 @@ // calculate answer percentage for question and all options int displayOrder = 1; for (Entry> questionToOptionEntry : questionsToOptionsMap.entrySet()) { - double totalAnswers = questionToOptionEntry.getValue().values().stream().mapToInt(Integer::intValue).sum(); - QbAnswersForOptionDTO dto = new QbAnswersForOptionDTO(questionToOptionEntry.getKey(), displayOrder++); + long qbQuestionUid = questionToOptionEntry.getKey(); + double totalAnswers = questionsToQuestionCountMap.get(qbQuestionUid); + QbAnswersForOptionDTO dto = new QbAnswersForOptionDTO(qbQuestionUid, displayOrder++); dtos.add(dto); for (Entry answersForOptionEntry : questionToOptionEntry.getValue().entrySet()) { Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java =================================================================== diff -u -re57933d5f4b215e037a34229c3d479f707cf5156 -r6b45c9ecb6999e184ca671297026bc6fa6faf9b5 --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision e57933d5f4b215e037a34229c3d479f707cf5156) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision 6b45c9ecb6999e184ca671297026bc6fa6faf9b5) @@ -693,8 +693,7 @@ } int numberOfAttempts = ScratchieServiceImpl.getNumberAttemptsForItem(item, visitLogs); - boolean isUnraveledOnFirstAttempt = (numberOfAttempts == 1) - && ScratchieServiceImpl.isItemUnraveled(item, logs); + boolean isUnraveledOnFirstAttempt = (numberOfAttempts == 1) && isItemUnraveled(item, logs); if (isUnraveledOnFirstAttempt) { count++; } @@ -958,7 +957,7 @@ optionDto.setScratched(isScratched); } - boolean isItemUnraveled = ScratchieServiceImpl.isItemUnraveled(item, userLogs); + boolean isItemUnraveled = isItemUnraveled(item, userLogs); item.setUnraveled(isItemUnraveled); } @@ -1045,7 +1044,7 @@ String answer = userLog.getAnswer(); optionDto.setAnswer(answer); optionDto.setDisplayOrder(-200); - boolean isCorrect = ScratchieServiceImpl.isItemUnraveledByAnswers(item, List.of(answer)); + boolean isCorrect = isItemUnraveled(item, userLog); optionDto.setCorrect(isCorrect); optionDto.setUserId(leader.getUserId()); item.getOptionDtos().add(optionDto); @@ -1075,7 +1074,7 @@ * uses logs from it (The main reason to have this parameter is to reduce number of queries to DB) * @return */ - private static boolean isItemUnraveled(ScratchieItem item, List userLogs) { + private boolean isItemUnraveled(ScratchieItem item, List userLogs) { boolean isItemUnraveled = false; if (QbQuestion.TYPE_MULTIPLE_CHOICE == item.getQbQuestion().getType() @@ -1098,21 +1097,35 @@ //VSA question } else { - List userAnswers = new ArrayList<>(); for (ScratchieAnswerVisitLog userLog : userLogs) { if (userLog.getQbToolQuestion().getUid().equals(item.getUid()) && StringUtils.isNotBlank(userLog.getAnswer())) { - userAnswers.add(userLog.getAnswer()); + isItemUnraveled = isItemUnraveled(item, userLog); + if (isItemUnraveled) { + break; + } } } - isItemUnraveled = ScratchieServiceImpl.isItemUnraveledByAnswers(item, userAnswers); } return isItemUnraveled; } - public static boolean isItemUnraveledByAnswers(ScratchieItem item, List userAnswers) { + private boolean isItemUnraveled(ScratchieItem item, ScratchieAnswerVisitLog userLog) { + QbOption correctAnswerGroup = ScratchieServiceImpl.isItemUnraveled(item, userLog.getAnswer()); + if (correctAnswerGroup != null) { + // link visit log to QB option + if (userLog.getQbOption() == null || userLog.getQbOption().getUid().equals(correctAnswerGroup.getUid())) { + userLog.setQbOption(correctAnswerGroup); + scratchieAnswerVisitDao.update(userLog); + } + return true; + } + return false; + } + + public static QbOption isItemUnraveled(ScratchieItem item, String userAnswer) { QbQuestion qbQuestion = item.getQbQuestion(); QbOption correctAnswersGroup = null; @@ -1124,24 +1137,22 @@ break; } // if the option has the highest grade, it is considered the correct one - if (correctAnswersGroup == null ? option.getMaxMark() > 0 : option.getMaxMark() > correctAnswersGroup.getMaxMark()) { + if (correctAnswersGroup == null ? option.getMaxMark() > 0 + : option.getMaxMark() > correctAnswersGroup.getMaxMark()) { correctAnswersGroup = option; } } if (correctAnswersGroup == null) { - return false; + return null; } - - + String name = correctAnswersGroup.getName(); - for (String userAnswer : userAnswers) { - String normalisedQuestionAnswer = QbUtils.normaliseVSAnswer(userAnswer, qbQuestion.isExactMatch()); - if (QbUtils.isVSAnswerAllocated(name, normalisedQuestionAnswer, qbQuestion.isCaseSensitive(), - qbQuestion.isExactMatch())) { - return true; - } + String normalisedQuestionAnswer = QbUtils.normaliseVSAnswer(userAnswer, qbQuestion.isExactMatch()); + if (QbUtils.isVSAnswerAllocated(name, normalisedQuestionAnswer, qbQuestion.isCaseSensitive(), + qbQuestion.isExactMatch())) { + return correctAnswersGroup; } - return false; + return null; } @Override @@ -1153,7 +1164,7 @@ // get lowest mark by default int mark = Integer.parseInt(presetMarks[presetMarks.length - 1]); // add mark only if an item was unravelled - if (ScratchieServiceImpl.isItemUnraveled(item, userLogs)) { + if (isItemUnraveled(item, userLogs)) { int itemAttempts = ScratchieServiceImpl.getNumberAttemptsForItem(item, userLogs); String markStr = (itemAttempts <= presetMarks.length) ? presetMarks[itemAttempts - 1] : presetMarks[presetMarks.length - 1]; @@ -1233,8 +1244,7 @@ optionUid = attempt.getQbOption().getUid(); } else { - String answer = attempt.getAnswer(); - boolean isCorrect = ScratchieServiceImpl.isItemUnraveledByAnswers(item, List.of(answer)); + boolean isCorrect = isItemUnraveled(item, attempt); boolean isFirstAnswersGroupCorrect = options.get(0).isCorrect(); optionUid = isCorrect && isFirstAnswersGroupCorrect || !isCorrect && !isFirstAnswersGroupCorrect @@ -2255,8 +2265,7 @@ // for displaying purposes if there is no attemps we assign -1 which will be shown as "-" mark = numberOfAttempts == 0 ? -1 : item.getMark(); - isUnraveledOnFirstAttempt = numberOfAttempts == 1 - && ScratchieServiceImpl.isItemUnraveled(item, logs); + isUnraveledOnFirstAttempt = numberOfAttempts == 1 && isItemUnraveled(item, logs); // find out options' sequential letters - A,B,C... for (ScratchieAnswerVisitLog visitLog : visitLogs) { @@ -2275,8 +2284,7 @@ } } else { - String answer = visitLog.getAnswer(); - boolean isCorrect = ScratchieServiceImpl.isItemUnraveledByAnswers(item, List.of(answer)); + boolean isCorrect = isItemUnraveled(item, visitLog); boolean isFirstAnswersGroupCorrect = qbQuestion.getQbOptions().get(0).isCorrect(); sequencialLetter = isCorrect && isFirstAnswersGroupCorrect Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/controller/LearningController.java =================================================================== diff -u -r0c2664b4727d894371762bc6a6e0fcc7ff6e94d2 -r6b45c9ecb6999e184ca671297026bc6fa6faf9b5 --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/controller/LearningController.java (.../LearningController.java) (revision 0c2664b4727d894371762bc6a6e0fcc7ff6e94d2) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/controller/LearningController.java (.../LearningController.java) (revision 6b45c9ecb6999e184ca671297026bc6fa6faf9b5) @@ -449,7 +449,7 @@ } // return whether option is correct or not - boolean isAnswerCorrect = ScratchieServiceImpl.isItemUnraveledByAnswers(item, List.of(answer)); + boolean isAnswerCorrect = ScratchieServiceImpl.isItemUnraveled(item, answer) != null; // return whether such answer was already logged (and also the answer hash which was logged previously) int loggedAnswerHash = -1;