From d27257fdffd0c4079d090cbb30194992cea0a807 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Thu, 19 Oct 2017 17:25:22 +0200
Subject: [PATCH] OO-2870: allow negative score for "choice" interactions like
 single and multiple choices, hotspot, hottext and match (drag and drop or
 not)

---
 .../qti21/manager/CorrectResponsesUtil.java   | 22 +++++++++
 .../model/xml/AssessmentTestBuilder.java      | 23 +++++++++-
 .../model/xml/AssessmentTestFactory.java      | 45 +++++++++++++++++++
 .../qti21/model/xml/QtiNodesExtractor.java    | 27 +++++++++++
 .../interactions/ChoiceScoreController.java   | 11 +++--
 .../interactions/FIBScoreController.java      |  1 -
 .../HotspotChoiceScoreController.java         | 12 +++--
 .../interactions/MatchScoreController.java    | 13 ++++--
 ...ChoiceInteractionStatisticsController.java | 19 +++++++-
 ...otspotInteractionStatisticsController.java |  8 +++-
 .../_content/statistics_interaction.html      |  2 +-
 11 files changed, 166 insertions(+), 17 deletions(-)

diff --git a/src/main/java/org/olat/ims/qti21/manager/CorrectResponsesUtil.java b/src/main/java/org/olat/ims/qti21/manager/CorrectResponsesUtil.java
index e2715fba17f..16a88771ceb 100644
--- a/src/main/java/org/olat/ims/qti21/manager/CorrectResponsesUtil.java
+++ b/src/main/java/org/olat/ims/qti21/manager/CorrectResponsesUtil.java
@@ -40,6 +40,9 @@ import uk.ac.ed.ph.jqtiplus.node.item.CorrectResponse;
 import uk.ac.ed.ph.jqtiplus.node.item.interaction.ChoiceInteraction;
 import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction;
 import uk.ac.ed.ph.jqtiplus.node.item.interaction.TextEntryInteraction;
+import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.Choice;
+import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.MapEntry;
+import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.Mapping;
 import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration;
 import uk.ac.ed.ph.jqtiplus.node.shared.FieldValue;
 import uk.ac.ed.ph.jqtiplus.types.Identifier;
@@ -342,4 +345,23 @@ public class CorrectResponsesUtil {
 		}
 		return null;
 	}
+	
+	public static final Double getMappedValue(AssessmentItem assessmentItem, Interaction interaction, Choice choice) {
+		ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(interaction.getResponseIdentifier());
+		if(responseDeclaration != null && responseDeclaration.getMapping() != null) {
+			Mapping mapping = responseDeclaration.getMapping();
+			if(mapping != null && mapping.getMapEntries() != null) {
+				for(MapEntry entry:mapping.getMapEntries()) {
+					SingleValue sValue = entry.getMapKey();
+					if(sValue instanceof IdentifierValue) {
+						Identifier identifier = ((IdentifierValue)sValue).identifierValue();
+						if(identifier.equals(choice.getIdentifier())) {
+							return entry.getMappedValue();
+						}
+					}
+				}
+			}
+		}
+		return null;
+	}
 }
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestBuilder.java
index f36e4a63ce4..c35d2bd9906 100644
--- a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestBuilder.java
+++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestBuilder.java
@@ -59,7 +59,7 @@ public class AssessmentTestBuilder {
 	private Double maxScore;
 	private Long maximumTimeLimits;
 	private OutcomeRule testScoreRule;
-	private OutcomeCondition cutValueRule;
+	private OutcomeCondition cutValueRule, minScoreRule;
 	
 	private TestFeedbackBuilder passedFeedback;
 	private TestFeedbackBuilder failedFeedback;
@@ -104,6 +104,12 @@ public class AssessmentTestBuilder {
 						cutValue = QtiNodesExtractor.extractCutValue(outcomeCondition.getOutcomeIf());
 						cutValueRule = outcomeCondition;
 					}
+					
+					boolean findMinIf = QtiNodesExtractor.findLtValue(outcomeCondition.getOutcomeIf(), QTI21Constants.MINSCORE_IDENTIFIER)
+							&& QtiNodesExtractor.findLtValue(outcomeCondition.getOutcomeIf(), QTI21Constants.SCORE_IDENTIFIER);
+					if(findMinIf) {
+						minScoreRule = outcomeCondition;
+					}
 				}
 			}
 		}
@@ -239,6 +245,15 @@ public class AssessmentTestBuilder {
 				assessmentTest.getOutcomeDeclarations().remove(maxScoreDeclaration);
 			}
 		}
+		
+		// add min. score
+		OutcomeDeclaration minScoreDeclaration = assessmentTest.getOutcomeDeclaration(QTI21Constants.MINSCORE_IDENTIFIER);
+		if(minScoreDeclaration == null) {
+			minScoreDeclaration = AssessmentTestFactory.createOutcomeDeclaration(assessmentTest, QTI21Constants.MINSCORE_IDENTIFIER, 0.0d);
+			assessmentTest.getOutcomeDeclarations().add(0, minScoreDeclaration);
+		} else {//update value
+			AssessmentTestFactory.updateDefaultValue(minScoreDeclaration, 0.0d);
+		}
 	}
 	
 	/* Overall score of this test
@@ -263,6 +278,12 @@ public class AssessmentTestBuilder {
 			assessmentTest.getOutcomeProcessing().getOutcomeRules().add(0, scoreRule);
 			testScoreRule = scoreRule;
 		}
+		
+		if(minScoreRule == null) {
+			OutcomeCondition scoreRule = AssessmentTestFactory.createMinScoreRule(assessmentTest);
+			assessmentTest.getOutcomeProcessing().getOutcomeRules().add(1, scoreRule);
+			minScoreRule = scoreRule;
+		}
 	}
 	
 	/*	Passed
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestFactory.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestFactory.java
index cacd6c0d513..735bad668e1 100644
--- a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestFactory.java
+++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestFactory.java
@@ -19,6 +19,10 @@
  */
 package org.olat.ims.qti21.model.xml;
 
+import static org.olat.ims.qti21.QTI21Constants.MINSCORE_CLX_IDENTIFIER;
+import static org.olat.ims.qti21.QTI21Constants.SCORE_CLX_IDENTIFIER;
+import static org.olat.ims.qti21.QTI21Constants.SCORE_IDENTIFIER;
+
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
@@ -35,6 +39,7 @@ import uk.ac.ed.ph.jqtiplus.node.expression.general.BaseValue;
 import uk.ac.ed.ph.jqtiplus.node.expression.general.Variable;
 import uk.ac.ed.ph.jqtiplus.node.expression.operator.And;
 import uk.ac.ed.ph.jqtiplus.node.expression.operator.Gte;
+import uk.ac.ed.ph.jqtiplus.node.expression.operator.Lt;
 import uk.ac.ed.ph.jqtiplus.node.expression.operator.Match;
 import uk.ac.ed.ph.jqtiplus.node.expression.operator.Multiple;
 import uk.ac.ed.ph.jqtiplus.node.expression.operator.Sum;
@@ -237,6 +242,46 @@ public class AssessmentTestFactory {
 		return outcomeCondition;
 	}
 	
+	/*
+    <outcomeCondition>
+      <outcomeIf>
+        <lt>
+          <variable identifier="SCORE"/>
+          <variable identifier="MINSCORE"/>
+        </lt>
+        <setOutcomeValue identifier="SCORE">
+          <variable identifier="MINSCORE"/>
+        </setOutcomeValue>
+      </outcomeIf>
+    </outcomeCondition>
+    */
+	public static OutcomeCondition createMinScoreRule(AssessmentTest assessmentTest) {
+		OutcomeCondition outcomeCondition = new OutcomeCondition(assessmentTest);
+		OutcomeIf outcomeIf = new OutcomeIf(outcomeCondition);
+		outcomeCondition.setOutcomeIf(outcomeIf);
+		
+		Lt lt = new Lt(outcomeIf);
+		outcomeIf.setExpression(lt);
+		
+		Variable scoreVar = new Variable(lt);
+		scoreVar.setIdentifier(SCORE_CLX_IDENTIFIER);
+		lt.getExpressions().add(scoreVar);
+		
+		Variable minScoreVar = new Variable(lt);
+		minScoreVar.setIdentifier(MINSCORE_CLX_IDENTIFIER);
+		lt.getExpressions().add(minScoreVar);
+		
+		SetOutcomeValue setOutcomeValue = new SetOutcomeValue(outcomeIf);
+		setOutcomeValue.setIdentifier(SCORE_IDENTIFIER);
+		
+		Variable minScoreOutcomeVar = new Variable(setOutcomeValue);
+		minScoreOutcomeVar.setIdentifier(MINSCORE_CLX_IDENTIFIER);
+		setOutcomeValue.setExpression(minScoreOutcomeVar);
+		outcomeIf.getOutcomeRules().add(setOutcomeValue);
+		
+		return outcomeCondition;
+	}
+	
 	/*
 	<sum>
 		<testVariables variableIdentifier="SCORE" />
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/QtiNodesExtractor.java b/src/main/java/org/olat/ims/qti21/model/xml/QtiNodesExtractor.java
index 50e65a8911c..fea7038682e 100644
--- a/src/main/java/org/olat/ims/qti21/model/xml/QtiNodesExtractor.java
+++ b/src/main/java/org/olat/ims/qti21/model/xml/QtiNodesExtractor.java
@@ -30,6 +30,8 @@ import org.olat.ims.qti21.QTI21Constants;
 
 import uk.ac.ed.ph.jqtiplus.node.expression.Expression;
 import uk.ac.ed.ph.jqtiplus.node.expression.general.BaseValue;
+import uk.ac.ed.ph.jqtiplus.node.expression.general.Variable;
+import uk.ac.ed.ph.jqtiplus.node.expression.operator.Lt;
 import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem;
 import uk.ac.ed.ph.jqtiplus.node.item.CorrectResponse;
 import uk.ac.ed.ph.jqtiplus.node.outcome.declaration.OutcomeDeclaration;
@@ -39,6 +41,7 @@ import uk.ac.ed.ph.jqtiplus.node.shared.declaration.DefaultValue;
 import uk.ac.ed.ph.jqtiplus.node.test.AssessmentTest;
 import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeCondition;
 import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeConditionChild;
+import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeConditionExpressionChild;
 import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeIf;
 import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeRule;
 import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.SetOutcomeValue;
@@ -168,6 +171,30 @@ public interface QtiNodesExtractor {
 		return false;
 	}
 	
+	public static boolean findLtValue(OutcomeConditionExpressionChild outcomeIfElse, Identifier identifier) {
+		if(outcomeIfElse == null
+				|| outcomeIfElse.getOutcomeRules() == null
+				|| outcomeIfElse.getOutcomeRules().isEmpty()) return false;
+		
+		List<Expression> expressions = outcomeIfElse.getExpressions();
+		for(Expression expression:expressions) {
+			if(expression instanceof Lt) {
+				Lt lt = (Lt)expression;
+				for(Expression ltExpression:lt.getExpressions()) {
+					if(ltExpression instanceof Variable) {
+						Variable variable = (Variable)ltExpression;
+						if(identifier.toString().equals(variable.getIdentifier().toString())) {
+							return true;
+						}
+					}
+				}
+			}
+			
+		}
+		
+		return false;
+	}
+	
 	public static Double extractCutValue(OutcomeIf outcomeIf) {
 		if(outcomeIf != null && outcomeIf.getExpressions().size() > 0) {
 			Expression gte = outcomeIf.getExpressions().get(0);
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/ChoiceScoreController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/ChoiceScoreController.java
index 00f33d343ed..d8d762dfd7b 100644
--- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/ChoiceScoreController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/ChoiceScoreController.java
@@ -103,9 +103,11 @@ public class ChoiceScoreController extends AssessmentItemRefEditorController imp
 		setFormContextHelp(contextHelpUrl);
 		super.initForm(formLayout, listener, ureq);
 		
-		minScoreEl = uifactory.addTextElement("min.score", "min.score", 8, "0.0", formLayout);
+		ScoreBuilder minScore = itemBuilder.getMinScoreBuilder();
+		String minValue = minScore == null ? "" : (minScore.getScore() == null ? "" : minScore.getScore().toString());
+		minScoreEl = uifactory.addTextElement("min.score", "min.score", 8, minValue, formLayout);
 		minScoreEl.setElementCssClass("o_sel_assessment_item_min_score");
-		minScoreEl.setEnabled(false);
+		minScoreEl.setEnabled(!restrictedEdit);
 		
 		ScoreBuilder maxScore = itemBuilder.getMaxScoreBuilder();
 		String maxValue = maxScore == null ? "" : (maxScore.getScore() == null ? "" : maxScore.getScore().toString());
@@ -194,6 +196,7 @@ public class ChoiceScoreController extends AssessmentItemRefEditorController imp
 	protected boolean validateFormLogic(UserRequest ureq) {
 		boolean allOk = true;
 		allOk &= validateDouble(maxScoreEl);
+		allOk &= validateDouble(minScoreEl);
 
 		if(assessmentModeEl.isOneSelected() && assessmentModeEl.isSelected(1)) {
 			for(ChoiceWrapper wrapper:wrappers) {
@@ -220,7 +223,9 @@ public class ChoiceScoreController extends AssessmentItemRefEditorController imp
 		String maxScoreValue = maxScoreEl.getValue();
 		Double maxScore = Double.parseDouble(maxScoreValue);
 		itemBuilder.setMaxScore(maxScore);
-		itemBuilder.setMinScore(new Double(0d));
+		String minScoreValue = minScoreEl.getValue();
+		Double minScore = Double.parseDouble(minScoreValue);
+		itemBuilder.setMinScore(minScore);
 		
 		if(assessmentModeEl.isOneSelected() && assessmentModeEl.isSelected(1)) {
 			itemBuilder.setScoreEvaluationMode(ScoreEvaluation.perAnswer);
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBScoreController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBScoreController.java
index 2c58c387b15..d8574bd6dc4 100644
--- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBScoreController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBScoreController.java
@@ -85,7 +85,6 @@ public class FIBScoreController extends AssessmentItemRefEditorController implem
 		super.initForm(formLayout, listener, ureq);
 		minScoreEl = uifactory.addTextElement("min.score", "min.score", 8, "0.0", formLayout);
 		minScoreEl.setEnabled(false);
-		minScoreEl.setEnabled(!restrictedEdit);
 		
 		ScoreBuilder maxScore = itemBuilder.getMaxScoreBuilder();
 		String maxValue = maxScore == null ? "" : (maxScore.getScore() == null ? "" : maxScore.getScore().toString());
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/HotspotChoiceScoreController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/HotspotChoiceScoreController.java
index 3354e792990..00e938b96e0 100644
--- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/HotspotChoiceScoreController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/HotspotChoiceScoreController.java
@@ -104,9 +104,10 @@ public class HotspotChoiceScoreController extends AssessmentItemRefEditorControl
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
 		super.initForm(formLayout, listener, ureq);
 		setFormContextHelp("Test editor QTI 2.1 in detail#details_testeditor_score");
-		
-		minScoreEl = uifactory.addTextElement("min.score", "min.score", 8, "0.0", formLayout);
-		minScoreEl.setEnabled(false);
+
+		ScoreBuilder minScore = itemBuilder.getMinScoreBuilder();
+		String minValue = minScore == null ? "" : (minScore.getScore() == null ? "" : minScore.getScore().toString());
+		minScoreEl = uifactory.addTextElement("min.score", "min.score", 8, minValue, formLayout);
 		minScoreEl.setEnabled(!restrictedEdit);
 		
 		ScoreBuilder maxScore = itemBuilder.getMaxScoreBuilder();
@@ -243,6 +244,7 @@ public class HotspotChoiceScoreController extends AssessmentItemRefEditorControl
 	protected boolean validateFormLogic(UserRequest ureq) {
 		boolean allOk = true;
 		allOk &= validateDouble(maxScoreEl);
+		allOk &= validateDouble(minScoreEl);
 
 		if(assessmentModeEl.isOneSelected() && assessmentModeEl.isSelected(1)) {
 			for(HotspotChoiceWrapper wrapper:wrappers) {
@@ -267,7 +269,9 @@ public class HotspotChoiceScoreController extends AssessmentItemRefEditorControl
 		String maxScoreValue = maxScoreEl.getValue();
 		Double maxScore = Double.parseDouble(maxScoreValue);
 		itemBuilder.setMaxScore(maxScore);
-		itemBuilder.setMinScore(new Double(0d));
+		String minScoreValue = minScoreEl.getValue();
+		Double minScore = Double.parseDouble(minScoreValue);
+		itemBuilder.setMinScore(minScore);
 		
 		if(assessmentModeEl.isOneSelected() && assessmentModeEl.isSelected(1)) {
 			itemBuilder.setScoreEvaluationMode(ScoreEvaluation.perAnswer);
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchScoreController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchScoreController.java
index 6c451a1c272..dd2377a914f 100644
--- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchScoreController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchScoreController.java
@@ -102,10 +102,12 @@ public class MatchScoreController extends AssessmentItemRefEditorController impl
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
 		super.initForm(formLayout, listener, ureq);
 		setFormContextHelp("Test editor QTI 2.1 in detail#details_testeditor_score");
-		
-		minScoreEl = uifactory.addTextElement("min.score", "min.score", 8, "0.0", formLayout);
+
+		ScoreBuilder minScore = itemBuilder.getMinScoreBuilder();
+		String minValue = minScore == null ? "" : (minScore.getScore() == null ? "" : minScore.getScore().toString());
+		minScoreEl = uifactory.addTextElement("min.score", "min.score", 8, minValue, formLayout);
 		minScoreEl.setElementCssClass("o_sel_assessment_item_min_score");
-		minScoreEl.setEnabled(false);
+		minScoreEl.setEnabled(!restrictedEdit);
 		
 		ScoreBuilder maxScore = itemBuilder.getMaxScoreBuilder();
 		String maxValue = maxScore == null ? "" : (maxScore.getScore() == null ? "" : maxScore.getScore().toString());
@@ -214,6 +216,7 @@ public class MatchScoreController extends AssessmentItemRefEditorController impl
 	protected boolean validateFormLogic(UserRequest ureq) {
 		boolean allOk = true;
 		allOk &= validateDouble(maxScoreEl);
+		allOk &= validateDouble(minScoreEl);
 
 		if(assessmentModeEl.isOneSelected() && assessmentModeEl.isSelected(1)) {
 			for(Map.Entry<DirectedPairValue, MatchScoreWrapper> entry:scoreWrappers.entrySet()) {
@@ -252,7 +255,9 @@ public class MatchScoreController extends AssessmentItemRefEditorController impl
 		String maxScoreValue = maxScoreEl.getValue();
 		Double maxScore = Double.parseDouble(maxScoreValue);
 		itemBuilder.setMaxScore(maxScore);
-		itemBuilder.setMinScore(new Double(0d));
+		String minScoreValue = minScoreEl.getValue();
+		Double minScore = Double.parseDouble(minScoreValue);
+		itemBuilder.setMinScore(minScore);
 		
 		if(assessmentModeEl.isOneSelected() && assessmentModeEl.isSelected(1)) {
 			itemBuilder.setScoreEvaluationMode(ScoreEvaluation.perAnswer);
diff --git a/src/main/java/org/olat/ims/qti21/ui/statistics/interactions/ChoiceInteractionStatisticsController.java b/src/main/java/org/olat/ims/qti21/ui/statistics/interactions/ChoiceInteractionStatisticsController.java
index 095debe0b96..09521ba74aa 100644
--- a/src/main/java/org/olat/ims/qti21/ui/statistics/interactions/ChoiceInteractionStatisticsController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/statistics/interactions/ChoiceInteractionStatisticsController.java
@@ -137,7 +137,12 @@ public abstract class ChoiceInteractionStatisticsController extends BasicControl
 				points = null;
 				cssColor = "bar_default";
 			} else {
-				points = correct ? 1.0f : 0.0f; //response.getPoints();
+				Double mappedValue = CorrectResponsesUtil.getMappedValue(assessmentItem, interaction, choice);
+				if(mappedValue != null) {
+					points = mappedValue.floatValue();
+				} else {
+					points = correct ? 1.0f : 0.0f; //response.getPoints();
+				}
 				cssColor = correct ? "bar_green" : "bar_red";
 			}
 
@@ -204,7 +209,17 @@ public abstract class ChoiceInteractionStatisticsController extends BasicControl
 			d2.add(wrongA, label);
 			d3.add(notAnswered, label);
 			
-			Float pointsObj = survey ? null : (correct ? 1.0f : 0.0f);
+			Float pointsObj;
+			if(survey) {
+				pointsObj = null;
+			} else {
+				Double mappedValue = CorrectResponsesUtil.getMappedValue(assessmentItem, interaction, choice);
+				if(mappedValue != null) {
+					pointsObj = mappedValue.floatValue();
+				} else {
+					pointsObj = correct ? 1.0f : 0.0f;
+				}
+			}
 			responseInfos.add(new ResponseInfos(label, text, pointsObj, correct, survey, false));
 		}
 
diff --git a/src/main/java/org/olat/ims/qti21/ui/statistics/interactions/HotspotInteractionStatisticsController.java b/src/main/java/org/olat/ims/qti21/ui/statistics/interactions/HotspotInteractionStatisticsController.java
index 7d1e21b9da8..3377dd1f3e3 100644
--- a/src/main/java/org/olat/ims/qti21/ui/statistics/interactions/HotspotInteractionStatisticsController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/statistics/interactions/HotspotInteractionStatisticsController.java
@@ -47,6 +47,7 @@ import org.olat.ims.qti.statistics.model.StatisticsItem;
 import org.olat.ims.qti.statistics.ui.ResponseInfos;
 import org.olat.ims.qti.statistics.ui.Series;
 import org.olat.ims.qti21.QTI21StatisticsManager;
+import org.olat.ims.qti21.manager.CorrectResponsesUtil;
 import org.olat.ims.qti21.model.statistics.HotspotChoiceStatistics;
 import org.olat.ims.qti21.ui.statistics.QTI21AssessmentItemStatisticsController;
 import org.olat.ims.qti21.ui.statistics.QTI21StatisticResourceResult;
@@ -190,7 +191,12 @@ public class HotspotInteractionStatisticsController extends BasicController {
 				points = null;
 				cssColor = "bar_default";
 			} else {
-				points = correct ? 1.0f : 0.0f; //response.getPoints();
+				Double mappedValue = CorrectResponsesUtil.getMappedValue(assessmentItem, interaction, choice);
+				if(mappedValue != null) {
+					points = mappedValue.floatValue();
+				} else {
+					points = correct ? 1.0f : 0.0f;
+				}
 				cssColor = correct ? "bar_green" : "bar_red";
 			}
 
diff --git a/src/main/java/org/olat/ims/qti21/ui/statistics/interactions/_content/statistics_interaction.html b/src/main/java/org/olat/ims/qti21/ui/statistics/interactions/_content/statistics_interaction.html
index a923dcd1027..4eb39de6ea9 100644
--- a/src/main/java/org/olat/ims/qti21/ui/statistics/interactions/_content/statistics_interaction.html
+++ b/src/main/java/org/olat/ims/qti21/ui/statistics/interactions/_content/statistics_interaction.html
@@ -34,7 +34,7 @@
 			#end
 		#end
 		<strong>$responseInfo.label.</strong> 
-		#if($responseInfo.points) $r.translate("answer.points", $responseInfo.formattedPoints) #end
+		#if($r.isNotNull($responseInfo.points)) $r.translate("answer.points", $responseInfo.formattedPoints) #end
 		#if($responseInfo.wrongAnswersAvailable)
 			<span class="o_qti_statistics_answer"><strong>$responseInfo.text</strong></span>
 			<p class="o_qti_statistics_wrong_answer">#if($responseInfo.survey)
-- 
GitLab