We use QTI 3.0 xml standard from 1edtech to define and process items and assessments: https://www.1edtech.org/standards/qti
One of the interaction types we have is the order interaction where a candidate needs to order certain answer options in the correct order. An example item looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0_v1p0.xsd" identifier="ITM-b2397501-b51e-4170-a2df-e472a78e52ac" title="Text Order" time-dependent="false" xml:lang="en" data-schemaVersion="2">
<qti-response-declaration identifier="RESPONSE" base-type="identifier" cardinality="ordered">
<qti-correct-response>
<qti-value>id-B12zjfLld_tNh5-</qti-value>
<qti-value>id-Whpp5gz8lqr0sL0</qti-value>
<qti-value>id-KJq4z0gk7a4XIIY</qti-value>
<qti-value>id-QQ0tsUPuC1nFrU0</qti-value>
</qti-correct-response>
</qti-response-declaration>
<qti-outcome-declaration identifier="SCORE" cardinality="single" base-type="float"/>
<qti-outcome-declaration identifier="MAXSCORE" cardinality="single" base-type="float">
<qti-default-value>
<qti-value>4</qti-value>
</qti-default-value>
</qti-outcome-declaration>
<qti-item-body xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0">
<div class="qti-layout-row">
<div class="qti-layout-col6 qti-layout-offset3">
<qti-order-interaction response-identifier="RESPONSE" orientation="vertical" shuffle="true">
<qti-prompt>
<h1>Text Order</h1>
<p>Define the correct order</p>
**your text** </qti-prompt>
<qti-simple-choice identifier="id-B12zjfLld_tNh5-">A</qti-simple-choice>
<qti-simple-choice identifier="id-Whpp5gz8lqr0sL0">B</qti-simple-choice>
<qti-simple-choice identifier="id-KJq4z0gk7a4XIIY">C</qti-simple-choice>
<qti-simple-choice identifier="id-QQ0tsUPuC1nFrU0">D</qti-simple-choice>
</qti-order-interaction>
</div>
</div>
</qti-item-body>
<qti-response-processing>
...
</qti-response-processing>
</qti-assessment-item>
In most examples online, this interaction type is scored with:
- the match correct template -> answer is correct or incorrect
https://github.com/1EdTech/qti-examples/blob/260ee986523543cb6a44d32e0c6821c34bbc001c/qtiv3-examples/packaging/items/order.xml#L14 - custom response processing where the response is compared with different possible answer options -> answer is correct, partial correct or incorrect
https://github.com/1EdTech/qti-examples/blob/260ee986523543cb6a44d32e0c6821c34bbc001c/qtiv3-examples/packaging/items/order_partial_scoring.xml#L14 - custom response processing where each individual given answer is validated and scored if it’s in the right position -> answer is correct, partial correct or incorrect.
https://github.com/1EdTech/qti-examples/blob/260ee986523543cb6a44d32e0c6821c34bbc001c/qtiv3-examples/packaging/BBQsTest/id-e475c3c922f6/order-maths.xml#L15
In the assessment taking business, there are more ways to score this type of item. The one we want to use is called the quotient rule. With this scoring rule, you check if an answer option is before the other answer option, no matter if there are other answer options in between.
For example, the correct order is: A B C D
The candidate answer is: A C B D
He will receive points because he puts A in front of C B D
He will receive points because he puts B in front of D
He will receive points because he puts C in front of D
He will receive points because he puts D at the end
There is a formula to calculate this:
SCORE = MAXSCORE * (S / ((K * (K – 1)) – S)))
Where K is the number of answer options to choose from and S the number of correct orders of all possible pairs of objects.
The formula itself is not so hard in QTI:
<qti-set-outcome-value identifier="SCORE">
<qti-product>
<qti-variable identifier="MAXSCORE"/>
<qti-divide>
<qti-variable identifier="S"/>
<qti-subtract>
<qti-product>
<qti-variable identifier="K"/>
<qti-subtract>
<qti-variable identifier="K"/>
<qti-base-value base-type="integer">1</qti-base-value>
</qti-subtract>
</qti-product>
<qti-variable identifier="S"/>
</qti-subtract>
</qti-divide>
</qti-product>
</qti-set-outcome-value>
And K can be determined when the QTI is generated as an outcome declaration:
<qti-outcome-declaration identifier="K" cardinality="single" base-type="integer">
<qti-default-value>
<qti-value>4</qti-value>
</qti-default-value>
</qti-outcome-declaration>
If you want to calculate S in code f.e. C#:
var correctResponse = assessmentItem.GetResponseDeclaration("RESPONSE").CorrectResponse.Values;
var candidateResponse = itemResult.GetResponseVariable("RESPONSE").CandidateResponse.Values;
var indexes = new List<int>();
// Find the indexes of the candidate answer when looked up in the correct answer
for (int i = 0; i < candidateResponse.Count; i++)
{
var index = correctResponse.FindIndex(r => r.ValueAsString == candidateResponse[i].ValueAsString);
indexes.Add(index);
}
// Increment s if the order of the answers is correct
int s = 0;
for (int i = 0; i < candidateResponse.Count; i++)
{
for (int j = i + 1; j < candidateResponse.Count; j++)
{
if (indexes[i] < indexes[j]) s++;
}
}
Actual Question
How to translate the calculation of S to QTI 3.0 response processing logic with the available operators?
https://www.imsglobal.org/sites/default/files/spec/qti/v3/info/index.html#Expr3
You need to start comparing pairs somehow. I came up with something like this:
<!-- Pairs (1,2), (1,3), (1,4) -->
<qti-response-condition>
<qti-response-if>
<qti-lt>
<qti-index n="1">
<qti-variable identifier="RESPONSE"/>
</qti-index>
<qti-index n="2">
<qti-variable identifier="RESPONSE"/>
</qti-index>
</qti-lt>
<qti-set-outcome-value identifier="S">
<qti-sum>
<qti-variable identifier="S"/>
<qti-base-value baseType="integer">1</qti-base-value>
</qti-sum>
</qti-set-outcome-value>
</qti-response-if>
</qti-response-condition>
<qti-response-condition>
<qti-response-if>
<qti-lt>
<qti-index n="1">
<qti-variable identifier="RESPONSE"/>
</qti-index>
<qti-index n="3">
<qti-variable identifier="RESPONSE"/>
</qti-index>
</qti-lt>
<qti-set-outcome-value identifier="S">
<qti-sum>
<qti-variable identifier="S"/>
<qti-base-value baseType="integer">1</qti-base-value>
</qti-sum>
</qti-set-outcome-value>
</qti-response-if>
</qti-response-condition>
<qti-response-condition>
<qti-response-if>
<qti-lt>
<qti-index n="1">
<qti-variable identifier="RESPONSE"/>
</qti-index>
<qti-index n="4">
<qti-variable identifier="RESPONSE"/>
</qti-index>
</qti-lt>
<qti-set-outcome-value identifier="S">
<qti-sum>
<qti-variable identifier="S"/>
<qti-base-value baseType="integer">1</qti-base-value>
</qti-sum>
</qti-set-outcome-value>
</qti-response-if>
</qti-response-condition>
<!-- Pairs (2,3), (2,4) -->
<qti-response-condition>
<qti-response-if>
<qti-lt>
<qti-index n="2">
<qti-variable identifier="RESPONSE"/>
</qti-index>
<qti-index n="3">
<qti-variable identifier="RESPONSE"/>
</qti-index>
</qti-lt>
<qti-set-outcome-value identifier="S">
<qti-sum>
<qti-variable identifier="S"/>
<qti-base-value baseType="integer">1</qti-base-value>
</qti-sum>
</qti-set-outcome-value>
</qti-response-if>
</qti-response-condition>
<qti-response-condition>
<qti-response-if>
<qti-lt>
<qti-index n="2">
<qti-variable identifier="RESPONSE"/>
</qti-index>
<qti-index n="4">
<qti-variable identifier="RESPONSE"/>
</qti-index>
</qti-lt>
<qti-set-outcome-value identifier="S">
<qti-sum>
<qti-variable identifier="S"/>
<qti-base-value baseType="integer">1</qti-base-value>
</qti-sum>
</qti-set-outcome-value>
</qti-response-if>
</qti-response-condition>
<!-- Pairs (3,4) -->
<qti-response-condition>
<qti-response-if>
<qti-lt>
<qti-index n="3">
<qti-variable identifier="RESPONSE"/>
</qti-index>
<qti-index n="4">
<qti-variable identifier="RESPONSE"/>
</qti-index>
</qti-lt>
<qti-set-outcome-value identifier="S">
<qti-sum>
<qti-variable identifier="S"/>
<qti-base-value baseType="integer">1</qti-base-value>
</qti-sum>
</qti-set-outcome-value>
</qti-response-if>
</qti-response-condition>
But I need to know for each given answer option what the index is in the correct response and then compare them. qti-index only selects an item from the array. It does not return the index.
Maybe I should determine the index in an outcome declaration upfront or somehow??