Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9e07323
SAK-52184 Samigo fix Tally errors in some Total Scores/Question Pool/…
ottenhoff Jan 29, 2026
ce1c3e1
coderabbit suggestions
ottenhoff Jan 30, 2026
98dd267
SAK-51207 samigo fix evaluation tally classification and sequencing
ottenhoff Feb 10, 2026
9feb0e4
SAK-52184 samigo add MC-MCSS-specific question pool stats
ottenhoff Feb 10, 2026
0e67604
SAK-52184 samigo harden stats and histogram null correctness handling
ottenhoff Feb 10, 2026
6159bfa
SAK-52184 samigo harden nearby tally and EMI edge cases
ottenhoff Feb 10, 2026
39de24d
SAK-52184 samigo treat missing analysis answers as incorrect
ottenhoff Feb 10, 2026
c45972d
SAK-52184 samigo align calc and hotspot tally classification
ottenhoff Feb 12, 2026
c166613
SAK-52184 samigo keep item analysis incorrect/no-answer disjoint
ottenhoff Feb 12, 2026
41c5821
SAK-52184 samigo harden MC-MCSS question pool classification
ottenhoff Feb 12, 2026
39d5b11
SAK-52184 samigo harden item analysis calc and survey tallying
ottenhoff Feb 12, 2026
32344cb
SAK-52184 samigo narrow survey-like fallback to null correctness
ottenhoff Feb 12, 2026
e8a969f
SAK-52184 Samigo unify submission classification in statistics
ottenhoff Feb 12, 2026
3f6f945
SAK-52184 Samigo centralize question-type capability registry
ottenhoff Feb 12, 2026
1ec2674
SAK-52184 Samigo reuse histogram classifier service and add MCSS test
ottenhoff Feb 12, 2026
8448a35
SAK-52184 Samigo guard export response correctness nulls
ottenhoff Feb 12, 2026
a0456b1
SAK-52184 Samigo clean redundant histogram listener guards
ottenhoff Feb 12, 2026
c3a0176
SAK-52184 Samigo avoid attachment load in total score tallies
ottenhoff Feb 12, 2026
e9da19c
SAK-52184 Samigo add stats coverage and clarify survey filters
ottenhoff Feb 12, 2026
529ceac
SAK-52184 Samigo add ignored-type statistics regression test
ottenhoff Feb 12, 2026
350c4b3
SAK-52184 Samigo restore CQ and NR detailed answer columns
ottenhoff Feb 12, 2026
a412714
SAK-52184 Samigo stabilize calculated detailed column order
ottenhoff Feb 12, 2026
dfd87a5
SAK-52184 Samigo harden item analysis export rows
ottenhoff Feb 13, 2026
92d3cea
SAK-52184 Samigo warn on unhandled answer-statistics type
ottenhoff Feb 13, 2026
63af231
remove dead code
ottenhoff Feb 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import javax.faces.bean.ManagedBean;
Expand Down Expand Up @@ -66,14 +69,18 @@
import org.sakaiproject.tool.assessment.data.dao.grading.AssessmentGradingData;
import org.sakaiproject.tool.assessment.data.dao.grading.ItemGradingData;
import org.sakaiproject.tool.assessment.data.ifc.assessment.AnswerIfc;
import org.sakaiproject.tool.assessment.data.ifc.assessment.PublishedAssessmentIfc;
import org.sakaiproject.tool.assessment.data.ifc.assessment.SectionDataIfc;
import org.sakaiproject.tool.assessment.data.ifc.assessment.ItemTextIfc;
import org.sakaiproject.tool.assessment.data.ifc.assessment.EvaluationModelIfc;
import org.sakaiproject.tool.assessment.data.ifc.shared.TypeIfc;
import org.sakaiproject.tool.assessment.facade.AgentFacade;
import org.sakaiproject.tool.assessment.facade.PublishedAssessmentFacade;
import org.sakaiproject.samigo.util.SamigoConstants;
import org.sakaiproject.tool.assessment.services.GradingService;
import org.sakaiproject.tool.assessment.services.PersistenceService;
import org.sakaiproject.tool.assessment.services.assessment.PublishedAssessmentService;
import org.sakaiproject.tool.assessment.services.assessment.StatisticsService;
import org.sakaiproject.tool.assessment.services.assessment.StatisticsService.SubmissionOutcome;
import org.sakaiproject.tool.assessment.shared.api.grading.GradingSectionAwareServiceAPI;
import org.sakaiproject.tool.assessment.shared.impl.grading.GradingSectionAwareServiceImpl;
import org.sakaiproject.tool.assessment.ui.bean.util.TotalScoresExportBean;
Expand Down Expand Up @@ -245,22 +252,31 @@ protected void init() {
}

public boolean getIsOneSelectionType() {
if (this.getPublishedAssessment() == null) {
return false;
PublishedAssessmentData currentPublishedAssessment = this.getPublishedAssessment();
if (currentPublishedAssessment == null) {
String currentPublishedId = StringUtils.trimToNull(getPublishedId());
if (currentPublishedId != null && !"0".equals(currentPublishedId)) {
PublishedAssessmentFacade publishedAssessmentFacade = new PublishedAssessmentService().getPublishedAssessment(currentPublishedId);
if (publishedAssessmentFacade != null && publishedAssessmentFacade.getData() instanceof PublishedAssessmentData) {
currentPublishedAssessment = (PublishedAssessmentData) publishedAssessmentFacade.getData();
this.publishedAssessment = currentPublishedAssessment;
}
}
}

if (currentPublishedAssessment == null) {
return isOneSelectionType;
} else {
for (Object sectionObject : this.getPublishedAssessment().getSectionArray()) {
for (Object sectionObject : currentPublishedAssessment.getSectionArray()) {
PublishedSectionData sectionData = (PublishedSectionData) sectionObject;
for (Object itemObject : sectionData.getItemArray()) {
PublishedItemData item = (PublishedItemData) itemObject;
boolean isMultipleChoice = item.getTypeId().equals(TypeIfc.MULTIPLE_CHOICE);
boolean isSingleSelection = item.getTypeId().equals(TypeIfc.MULTIPLE_CORRECT_SINGLE_SELECTION);
boolean isTrueFalseQuestionType = item.getTypeId().equals(TypeIfc.TRUE_FALSE);
if (!isMultipleChoice && !isSingleSelection && !isTrueFalseQuestionType) {
return false;
if (isTallyableItemType(item.getTypeId())) {
return true;
}
}
}
return true;
return false;
}
}

Expand All @@ -279,30 +295,41 @@ public Map getResults() {
this.setResultsAlreadyCalculated(true);
// Instance a new PublishedAssessmentService to get all the published Answer for each student
PublishedAssessmentService pubAssessmentService = new PublishedAssessmentService();
Map publishedAnswerHash = pubAssessmentService.preparePublishedAnswerHash(pubAssessmentService.getPublishedAssessment(this.getPublishedId()));
PublishedAssessmentIfc publishedAssessmentData = pubAssessmentService.getPublishedAssessment(this.getPublishedId());
Map publishedAnswerHash = pubAssessmentService.preparePublishedAnswerHash(publishedAssessmentData);
// Instance a new GradingService to get all the student responses
GradingService gradingService = new GradingService();
StatisticsService statisticsService = new StatisticsService();
Map<Long, List<Integer>> resultsByUser = new HashMap<>();
List<PublishedItemData> tallyableItems = getTallyableItems(publishedAssessmentData);
Map<Long, AnswerIfc> answersById = new HashMap<>();
for (Object answerObject : publishedAnswerHash.values()) {
AnswerIfc answer = (AnswerIfc) answerObject;
if (answer != null && answer.getId() != null) {
answersById.put(answer.getId(), answer);
}
}
// For each agent (student) we will search the correct/incorrect/empty responses
for (Object object : agents) {
AgentResults agentResults = (AgentResults) object;
if (agentResults.getAssessmentGradingId() != -1) {
// Getting the responses for that student (agentResults)
AssessmentGradingData assessmentGradingAux = gradingService.load(agentResults.getAssessmentGradingId().toString());
// Tallying needs item gradings only; skip attachment loading to avoid extra per-student DB work.
AssessmentGradingData assessmentGradingAux = gradingService.load(agentResults.getAssessmentGradingId().toString(), false);
List<Integer> resultsAux = new ArrayList<>(Collections.nCopies(3, 0));
for (ItemGradingData item : assessmentGradingAux.getItemGradingSet()) {
if (item.getPublishedAnswerId() == null) { // If it does not have publishedAnswerId that means it is empty
Map<Long, List<ItemGradingData>> gradingByItem = groupGradingsByItem(assessmentGradingAux);
for (PublishedItemData item : tallyableItems) {
Long itemId = item.getItemId();
List<ItemGradingData> gradingList = gradingByItem.get(itemId);
if (hasRandomDrawPart && gradingList == null) {
continue;
}
SubmissionOutcome submissionOutcome = statisticsService.classifySubmission(item, gradingList, answersById);
if (submissionOutcome == SubmissionOutcome.CORRECT) {
resultsAux.set(0, resultsAux.get(0) + 1);
} else if (submissionOutcome == SubmissionOutcome.INCORRECT) {
resultsAux.set(1, resultsAux.get(1) + 1);
} else if (submissionOutcome == SubmissionOutcome.BLANK) {
resultsAux.set(2, resultsAux.get(2) + 1);
} else { // If it has publishedAnswerId that means has response
// If it has response we will get the answer from publishedAnswerHash and if it is correct or incorrect
AnswerIfc answer = (AnswerIfc) publishedAnswerHash.get(item.getPublishedAnswerId());
if (!answer.getIsCorrect()) {
// For incorrect answers cases
resultsAux.set(1, ((int) resultsAux.get(1)) + 1);
} else {
// For correct answers cases
resultsAux.set(0, ((int) resultsAux.get(0)) + 1);
}
}
}
resultsByUser.put(agentResults.getAssessmentGradingId(), resultsAux);
Expand All @@ -312,6 +339,50 @@ public Map getResults() {
}
return results;
}

private boolean isTallyableItemType(Long typeId) {
return StatisticsService.supportsTotalScoresTally(typeId);
}

private List<PublishedItemData> getTallyableItems(PublishedAssessmentIfc publishedAssessmentData) {
List<PublishedItemData> tallyableItems = new ArrayList<>();
if (publishedAssessmentData == null) {
return tallyableItems;
}
for (Object sectionObject : publishedAssessmentData.getSectionArray()) {
SectionDataIfc sectionData = (SectionDataIfc) sectionObject;
for (Object itemObject : sectionData.getItemArray()) {
PublishedItemData item = (PublishedItemData) itemObject;
if (isTallyableItemType(item.getTypeId())) {
tallyableItems.add(item);
}
}
}
return tallyableItems;
}

private Map<Long, List<ItemGradingData>> groupGradingsByItem(AssessmentGradingData assessmentGradingData) {
Map<Long, List<ItemGradingData>> gradingByItem = new HashMap<>();
if (assessmentGradingData == null || assessmentGradingData.getItemGradingSet() == null) {
return gradingByItem;
}
for (ItemGradingData gradingData : (Set<ItemGradingData>) assessmentGradingData.getItemGradingSet()) {
if (gradingData == null) {
continue;
}
Long itemId = gradingData.getPublishedItemId();
if (itemId == null) {
continue;
}
List<ItemGradingData> gradingList = gradingByItem.get(itemId);
if (gradingList == null) {
gradingList = new ArrayList<>();
gradingByItem.put(itemId, gradingList);
}
gradingList.add(gradingData);
}
return gradingByItem;
}

// Following three methods are for interface PhaseAware
public void endProcessValidators() {
Expand Down
Loading
Loading