qti 2.19.0 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/qti/v1/models/choices/fill_blank_choice.rb +4 -0
- data/lib/qti/v1/models/interactions/base_fill_blank_interaction.rb +38 -1
- data/lib/qti/v1/models/interactions/base_interaction.rb +9 -0
- data/lib/qti/v1/models/interactions/fill_blank_interaction.rb +83 -2
- data/lib/qti/v1/models/scoring_data.rb +4 -2
- data/lib/qti/version.rb +1 -1
- data/spec/fixtures/items_1.2/nq_multiple_fib_answer_types.xml +86 -0
- data/spec/fixtures/items_1.2/nq_multiple_fib_scoring_algorithms.xml +130 -0
- data/spec/lib/qti/v1/models/choices/fill_blank_choice_spec.rb +14 -0
- data/spec/lib/qti/v1/models/interactions/base_interaction_spec.rb +16 -0
- data/spec/lib/qti/v1/models/interactions/fill_blank_interaction_spec.rb +168 -8
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1c38dab5972627b0bf1cd428bbde63a1363365dab50bf9726f6b8a35aa605a2a
|
4
|
+
data.tar.gz: b00506a2ada125d8853f42fa7b5d328b68985b9821be063e5371a03f6333ef90
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4ac85f4bc7890cf07b860b9ddeb8874298dd18fc04136c3d5432e4e0513ec07fb15971657f08ef120ffa8040c1361d3d48b4a45457cb261415a8e1ce9b49358
|
7
|
+
data.tar.gz: b2445a01bdeb590a23f2f89a050215b4b19290a436d2d49ea14d1d52a2c6be2b9c486e85095d68153ca5ace4c894d3a1524276d0189ccaae4024fffe2a1c5dad
|
@@ -39,19 +39,33 @@ module Qti
|
|
39
39
|
|
40
40
|
def blank_id(stem_item)
|
41
41
|
return stem_item unless canvas_custom_fitb?
|
42
|
-
canvas_blank_id(stem_item)
|
42
|
+
return canvas_blank_id(stem_item) unless new_quizzes_fib?
|
43
|
+
nq_blank_id(stem_item)
|
43
44
|
end
|
44
45
|
|
45
46
|
def blank_value(blank_id)
|
47
|
+
return nq_blank_value(blank_id) if new_quizzes_fib?
|
48
|
+
|
46
49
|
blank = canvas_fib_responses.find { |response| response[:id] == blank_id }
|
47
50
|
correct_choice = blank[:choices].find { |c| c[:id] == correct_choice_id(blank) }
|
48
51
|
(correct_choice || {})[:item_body] || blank&.dig(:choices, 0, :item_body)
|
49
52
|
end
|
50
53
|
|
54
|
+
# The correct answer value is not necessarily the first one in NQ
|
55
|
+
def nq_blank_value(blank_id)
|
56
|
+
blank = canvas_fib_responses.find { |response| response[:id] == blank_id }
|
57
|
+
correct_choice = blank[:choices].find { |choice| choice[:id] == correct_answer_map[blank_id] }
|
58
|
+
correct_choice[:item_body]
|
59
|
+
end
|
60
|
+
|
51
61
|
def canvas_custom_fitb?
|
52
62
|
@canvas_custom_fitb ||= BaseInteraction.canvas_custom_fitb?(@node)
|
53
63
|
end
|
54
64
|
|
65
|
+
def new_quizzes_fib?
|
66
|
+
@new_quizzes_fib ||= BaseInteraction.new_quizzes_fib?(@node)
|
67
|
+
end
|
68
|
+
|
55
69
|
def canvas_blank_id(stem_item)
|
56
70
|
blank_id = nil
|
57
71
|
node.xpath('.//xmlns:response_lid/xmlns:material').children.map do |response_lid_node|
|
@@ -62,6 +76,14 @@ module Qti
|
|
62
76
|
blank_id
|
63
77
|
end
|
64
78
|
|
79
|
+
def nq_blank_id(stem_item)
|
80
|
+
blank_id = nil
|
81
|
+
node.xpath('.//xmlns:response_lid').map do |resp|
|
82
|
+
blank_id = resp[:ident] if resp[:ident] == "response_#{stem_item}"
|
83
|
+
end
|
84
|
+
blank_id
|
85
|
+
end
|
86
|
+
|
65
87
|
def canvas_fib_responses
|
66
88
|
@base_canvas_blanks ||= node.xpath('.//xmlns:response_lid').map do |resp|
|
67
89
|
index = 0
|
@@ -80,8 +102,23 @@ module Qti
|
|
80
102
|
@canvas_fib_response_ids ||= canvas_fib_responses.map { |b| "[#{b[:id].sub(/^response_/, '')}]" }
|
81
103
|
end
|
82
104
|
|
105
|
+
def correct_answer_map
|
106
|
+
@correct_answer_map ||= correct_answers
|
107
|
+
end
|
108
|
+
|
83
109
|
private
|
84
110
|
|
111
|
+
# Maps response ids to their correct answer's id
|
112
|
+
def correct_answers
|
113
|
+
correct_answers = {}
|
114
|
+
|
115
|
+
node.xpath('.//xmlns:varequal').each do |correct_answer|
|
116
|
+
correct_answers[correct_answer.attributes['respident']&.value] = correct_answer.text
|
117
|
+
end
|
118
|
+
|
119
|
+
correct_answers
|
120
|
+
end
|
121
|
+
|
85
122
|
def correct_choice_id(blank)
|
86
123
|
node.xpath('.//xmlns:resprocessing/xmlns:respcondition/xmlns:conditionvar/xmlns:varequal')
|
87
124
|
.find { |varequal| varequal.attribute('respident').value == blank[:id] }&.text
|
@@ -20,6 +20,15 @@ module Qti
|
|
20
20
|
question_type(node) == 'fill_in_multiple_blanks_question'
|
21
21
|
end
|
22
22
|
|
23
|
+
def self.new_quizzes_fib?(node)
|
24
|
+
return false unless canvas_multiple_fib?(node)
|
25
|
+
|
26
|
+
first_match = node.at_xpath('.//xmlns:response_label')
|
27
|
+
return false if first_match.blank?
|
28
|
+
|
29
|
+
first_match.attr('scoring_algorithm').present?
|
30
|
+
end
|
31
|
+
|
23
32
|
def self.question_type(node)
|
24
33
|
path = './/xmlns:qtimetadatafield/xmlns:fieldlabel' \
|
25
34
|
'[text()="question_type"]/../xmlns:fieldentry'
|
@@ -30,6 +30,10 @@ module Qti
|
|
30
30
|
@canvas_multiple_fib ||= BaseInteraction.canvas_multiple_fib?(@node)
|
31
31
|
end
|
32
32
|
|
33
|
+
def new_quizzes_fib?
|
34
|
+
@new_quizzes_fib ||= BaseInteraction.new_quizzes_fib?(@node)
|
35
|
+
end
|
36
|
+
|
33
37
|
def stem_items
|
34
38
|
if canvas_multiple_fib?
|
35
39
|
canvas_stem_items(node.at_xpath('.//xmlns:mattext').text)
|
@@ -76,17 +80,55 @@ module Qti
|
|
76
80
|
end
|
77
81
|
|
78
82
|
def scoring_data_structs
|
79
|
-
answer_nodes.map do |value_node|
|
83
|
+
@scoring_data_structs ||= answer_nodes.map do |value_node|
|
80
84
|
ScoringData.new(
|
81
85
|
value_node.content,
|
82
86
|
rcardinality,
|
83
87
|
id: scoring_data_id(value_node),
|
84
88
|
case: scoring_data_case(value_node),
|
85
|
-
parent_identifier: value_node.parent.parent.attributes['ident']&.value
|
89
|
+
parent_identifier: value_node.parent.parent.attributes['ident']&.value,
|
90
|
+
scoring_algorithm: scoring_data_algorithm(value_node),
|
91
|
+
answer_type: scoring_data_answer_type(value_node),
|
92
|
+
scoring_options: scoring_data_options(value_node)
|
86
93
|
)
|
87
94
|
end
|
88
95
|
end
|
89
96
|
|
97
|
+
def wordbank_answer_present?
|
98
|
+
return false unless first_wordbank_answer
|
99
|
+
|
100
|
+
true
|
101
|
+
end
|
102
|
+
|
103
|
+
def wordbank_allow_reuse?
|
104
|
+
return unless wordbank_answer_present?
|
105
|
+
|
106
|
+
wordbank_struct = scoring_data_structs.find { |struct| struct.answer_type == 'wordbank' }
|
107
|
+
return false unless wordbank_struct.scoring_options['allow_reuse'] == 'true'
|
108
|
+
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
def wordbank_choices
|
113
|
+
return unless wordbank_answer_present?
|
114
|
+
|
115
|
+
choices = []
|
116
|
+
|
117
|
+
wordbank_answers = all_wordbank_answers.map do |node|
|
118
|
+
V1::Models::Choices::FillBlankChoice.new(node, self)
|
119
|
+
end
|
120
|
+
|
121
|
+
wordbank_answers.each do |wordbank_answer|
|
122
|
+
choices.push({ id: wordbank_answer.identifier, item_body: wordbank_answer.item_body })
|
123
|
+
end
|
124
|
+
|
125
|
+
choices
|
126
|
+
end
|
127
|
+
|
128
|
+
def correct_answer_map
|
129
|
+
@correct_answer_map ||= super
|
130
|
+
end
|
131
|
+
|
90
132
|
private
|
91
133
|
|
92
134
|
def rcardinality
|
@@ -110,6 +152,45 @@ module Qti
|
|
110
152
|
node.attributes['case']&.value || 'no'
|
111
153
|
end
|
112
154
|
|
155
|
+
def scoring_data_algorithm(node)
|
156
|
+
node.attributes['scoring_algorithm']&.value || 'TextInChoices'
|
157
|
+
end
|
158
|
+
|
159
|
+
def scoring_data_answer_type(node)
|
160
|
+
node.attributes['answer_type']&.value || 'openEntry'
|
161
|
+
end
|
162
|
+
|
163
|
+
def scoring_data_options(node)
|
164
|
+
options = {}
|
165
|
+
algorithm = scoring_data_algorithm(node)
|
166
|
+
type = scoring_data_answer_type(node)
|
167
|
+
|
168
|
+
if algorithm == 'TextCloseEnough'
|
169
|
+
options['levenshtein_distance'] = levenshtein_distance(node)
|
170
|
+
options['ignore_case'] = ignore_case(node)
|
171
|
+
end
|
172
|
+
|
173
|
+
options['allow_reuse'] = node.attributes['allow_reuse']&.value if type == 'wordbank'
|
174
|
+
|
175
|
+
options
|
176
|
+
end
|
177
|
+
|
178
|
+
def levenshtein_distance(node)
|
179
|
+
node.attributes['levenshtein_distance']&.value || '1'
|
180
|
+
end
|
181
|
+
|
182
|
+
def ignore_case(node)
|
183
|
+
node.attributes['ignore_case']&.value || 'false'
|
184
|
+
end
|
185
|
+
|
186
|
+
def first_wordbank_answer
|
187
|
+
@first_wordbank_answer ||= answer_nodes.find { |node| scoring_data_answer_type(node) == 'wordbank' }
|
188
|
+
end
|
189
|
+
|
190
|
+
def all_wordbank_answers
|
191
|
+
@all_wordbank_answers ||= answer_nodes.find_all { |node| scoring_data_answer_type(node) == 'wordbank' }
|
192
|
+
end
|
193
|
+
|
113
194
|
def qti_stem_item(index, stem_item)
|
114
195
|
if stem_item.xpath('./xmlns:render_fib').present?
|
115
196
|
blank_id = stem_item.attributes['ident'].value
|
@@ -1,9 +1,11 @@
|
|
1
1
|
module Qti
|
2
2
|
module V1
|
3
3
|
module Models
|
4
|
-
ScoringData = Struct.new(:values, :rcardinality, :id, :case, :parent_identifier
|
4
|
+
ScoringData = Struct.new(:values, :rcardinality, :id, :case, :parent_identifier, :scoring_algorithm,
|
5
|
+
:answer_type, :scoring_options) do
|
5
6
|
def initialize(values, rcardinality, options = {})
|
6
|
-
super(values, rcardinality, options[:id], options[:case], options[:parent_identifier]
|
7
|
+
super(values, rcardinality, options[:id], options[:case], options[:parent_identifier],
|
8
|
+
options[:scoring_algorithm], options[:answer_type], options[:scoring_options])
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|
data/lib/qti/version.rb
CHANGED
@@ -0,0 +1,86 @@
|
|
1
|
+
<?xml version="1.0" encoding="ISO-8859-1"?>
|
2
|
+
<questestinterop xmlns="http://www.imsglobal.org/xsd/ims_qtiasiv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd">
|
3
|
+
<item ident="a7ca973956582afc57a81192b444632c" title="NQ Fill in Multiple Blanks Answer Types">
|
4
|
+
<itemmetadata>
|
5
|
+
<qtimetadata>
|
6
|
+
<qtimetadatafield>
|
7
|
+
<fieldlabel>question_type</fieldlabel>
|
8
|
+
<fieldentry>fill_in_multiple_blanks_question</fieldentry>
|
9
|
+
</qtimetadatafield>
|
10
|
+
<qtimetadatafield>
|
11
|
+
<fieldlabel>points_possible</fieldlabel>
|
12
|
+
<fieldentry>1.0</fieldentry>
|
13
|
+
</qtimetadatafield>
|
14
|
+
<qtimetadatafield>
|
15
|
+
<fieldlabel>original_answer_ids</fieldlabel>
|
16
|
+
<fieldentry>["378646a8-b823-4e5b-beb8-19ca63f1f355", "c9400bfb-78ea-45b6-b5a5-e3311f6d5ed0", "76350749-49ac-4094-966b-c4e1f12d54bc", "cd11d826-906d-4dc4-b78d-d66516eb94ce", "bae0bd4f-2327-4a3e-b29f-199e1e279e91"]</fieldentry>
|
17
|
+
</qtimetadatafield>
|
18
|
+
<qtimetadatafield>
|
19
|
+
<fieldlabel>assessment_question_identifierref</fieldlabel>
|
20
|
+
<fieldentry>1c4b07be8a149dfa8f6a263a548cdd84</fieldentry>
|
21
|
+
</qtimetadatafield>
|
22
|
+
</qtimetadata>
|
23
|
+
</itemmetadata>
|
24
|
+
<presentation>
|
25
|
+
<material>
|
26
|
+
<mattext texttype="text/html"><p>Roses are [a20629ed-0c0b-4959-b565-a80c0f199602] and [ab37a945-ebad-4787-a356-66c3c91efcc6].</p></mattext>
|
27
|
+
</material>
|
28
|
+
<response_lid ident="response_a20629ed-0c0b-4959-b565-a80c0f199602">
|
29
|
+
<material>
|
30
|
+
<mattext>black</mattext>
|
31
|
+
</material>
|
32
|
+
<render_choice>
|
33
|
+
<response_label ident="378646a8-b823-4e5b-beb8-19ca63f1f355" position="1" scoring_algorithm='Equivalence' answer_type='dropdown'>
|
34
|
+
<material>
|
35
|
+
<mattext texttype="text/plain">black</mattext>
|
36
|
+
</material>
|
37
|
+
</response_label>
|
38
|
+
<response_label ident="c9400bfb-78ea-45b6-b5a5-e3311f6d5ed0" position="2" scoring_algorithm='Equivalence' answer_type='dropdown'>
|
39
|
+
<material>
|
40
|
+
<mattext texttype="text/plain">violet</mattext>
|
41
|
+
</material>
|
42
|
+
</response_label>
|
43
|
+
<response_label ident="76350749-49ac-4094-966b-c4e1f12d54bc" position="3" scoring_algorithm='Equivalence' answer_type='dropdown'>
|
44
|
+
<material>
|
45
|
+
<mattext texttype="text/plain">grey</mattext>
|
46
|
+
</material>
|
47
|
+
</response_label>
|
48
|
+
</render_choice>
|
49
|
+
</response_lid>
|
50
|
+
<response_lid ident="response_ab37a945-ebad-4787-a356-66c3c91efcc6">
|
51
|
+
<material>
|
52
|
+
<mattext>white</mattext>
|
53
|
+
</material>
|
54
|
+
<render_choice>
|
55
|
+
<response_label ident="cd11d826-906d-4dc4-b78d-d66516eb94ce" scoring_algorithm='TextEquivalence' answer_type='wordbank' allow_reuse='true'>
|
56
|
+
<material>
|
57
|
+
<mattext texttype="text/plain">brown</mattext>
|
58
|
+
</material>
|
59
|
+
</response_label>
|
60
|
+
<response_label ident="bae0bd4f-2327-4a3e-b29f-199e1e279e91" scoring_algorithm='TextEquivalence' answer_type='wordbank' allow_reuse='true'>
|
61
|
+
<material>
|
62
|
+
<mattext texttype="text/plain">white</mattext>
|
63
|
+
</material>
|
64
|
+
</response_label>
|
65
|
+
</render_choice>
|
66
|
+
</response_lid>
|
67
|
+
</presentation>
|
68
|
+
<resprocessing>
|
69
|
+
<outcomes>
|
70
|
+
<decvar maxvalue="100" minvalue="0" varname="SCORE" vartype="Decimal"/>
|
71
|
+
</outcomes>
|
72
|
+
<respcondition>
|
73
|
+
<conditionvar>
|
74
|
+
<varequal respident="response_a20629ed-0c0b-4959-b565-a80c0f199602">378646a8-b823-4e5b-beb8-19ca63f1f355</varequal>
|
75
|
+
</conditionvar>
|
76
|
+
<setvar action="Add" varname="SCORE">50.00</setvar>
|
77
|
+
</respcondition>
|
78
|
+
<respcondition>
|
79
|
+
<conditionvar>
|
80
|
+
<varequal respident="response_ab37a945-ebad-4787-a356-66c3c91efcc6">bae0bd4f-2327-4a3e-b29f-199e1e279e91</varequal>
|
81
|
+
</conditionvar>
|
82
|
+
<setvar action="Add" varname="SCORE">50.00</setvar>
|
83
|
+
</respcondition>
|
84
|
+
</resprocessing>
|
85
|
+
</item>
|
86
|
+
</questestinterop>
|
@@ -0,0 +1,130 @@
|
|
1
|
+
<?xml version="1.0" encoding="ISO-8859-1"?>
|
2
|
+
<questestinterop xmlns="http://www.imsglobal.org/xsd/ims_qtiasiv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd">
|
3
|
+
<item ident="97b0baae152f137d14503e2a2125c4d0" title="NQ Fill in Multiple Blanks Scoring Algorithms">
|
4
|
+
<itemmetadata>
|
5
|
+
<qtimetadata>
|
6
|
+
<qtimetadatafield>
|
7
|
+
<fieldlabel>question_type</fieldlabel>
|
8
|
+
<fieldentry>fill_in_multiple_blanks_question</fieldentry>
|
9
|
+
</qtimetadatafield>
|
10
|
+
<qtimetadatafield>
|
11
|
+
<fieldlabel>points_possible</fieldlabel>
|
12
|
+
<fieldentry>1.0</fieldentry>
|
13
|
+
</qtimetadatafield>
|
14
|
+
<qtimetadatafield>
|
15
|
+
<fieldlabel>original_answer_ids</fieldlabel>
|
16
|
+
<fieldentry>["bafd8242-ecdb-4158-a6d3-4ff15e016cb8-0", "d719aee0-6ce0-462c-9c0d-be63ba8d3408-0", "ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90-0", "41dfd716-8fd9-466a-97fa-33d353e44b42-0", "41dfd716-8fd9-466a-97fa-33d353e44b42-1", "a053119f-6a61-4372-ac79-4b2a7de0232f-0"]</fieldentry>
|
17
|
+
</qtimetadatafield>
|
18
|
+
<qtimetadatafield>
|
19
|
+
<fieldlabel>assessment_question_identifierref</fieldlabel>
|
20
|
+
<fieldentry>eaeec2d07f83998ee7b317ffde86dbc0</fieldentry>
|
21
|
+
</qtimetadatafield>
|
22
|
+
</qtimetadata>
|
23
|
+
</itemmetadata>
|
24
|
+
<presentation>
|
25
|
+
<material>
|
26
|
+
<mattext texttype="text/html"><p>Roses are [bafd8242-ecdb-4158-a6d3-4ff15e016cb8], [d719aee0-6ce0-462c-9c0d-be63ba8d3408], [ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90], [41dfd716-8fd9-466a-97fa-33d353e44b42], and [a053119f-6a61-4372-ac79-4b2a7de0232f].</p></mattext>
|
27
|
+
</material>
|
28
|
+
<response_lid ident="response_bafd8242-ecdb-4158-a6d3-4ff15e016cb8">
|
29
|
+
<material>
|
30
|
+
<mattext>red</mattext>
|
31
|
+
</material>
|
32
|
+
<render_choice>
|
33
|
+
<response_label ident="bafd8242-ecdb-4158-a6d3-4ff15e016cb8-0" scoring_algorithm='TextContainsAnswer' answer_type='openEntry'>
|
34
|
+
<material>
|
35
|
+
<mattext texttype="text/plain">red</mattext>
|
36
|
+
</material>
|
37
|
+
</response_label>
|
38
|
+
</render_choice>
|
39
|
+
</response_lid>
|
40
|
+
<response_lid ident="response_d719aee0-6ce0-462c-9c0d-be63ba8d3408">
|
41
|
+
<material>
|
42
|
+
<mattext>blue</mattext>
|
43
|
+
</material>
|
44
|
+
<render_choice>
|
45
|
+
<response_label ident="d719aee0-6ce0-462c-9c0d-be63ba8d3408-0" scoring_algorithm='TextCloseEnough' answer_type='openEntry' levenshtein_distance='1' ignore_case='true'>
|
46
|
+
<material>
|
47
|
+
<mattext texttype="text/plain">blue</mattext>
|
48
|
+
</material>
|
49
|
+
</response_label>
|
50
|
+
</render_choice>
|
51
|
+
</response_lid>
|
52
|
+
<response_lid ident="response_ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90">
|
53
|
+
<material>
|
54
|
+
<mattext>green</mattext>
|
55
|
+
</material>
|
56
|
+
<render_choice>
|
57
|
+
<response_label ident="ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90-0" scoring_algorithm='TextEquivalence' answer_type='openEntry'>
|
58
|
+
<material>
|
59
|
+
<mattext texttype="text/plain">green</mattext>
|
60
|
+
</material>
|
61
|
+
</response_label>
|
62
|
+
</render_choice>
|
63
|
+
</response_lid>
|
64
|
+
<response_lid ident="response_41dfd716-8fd9-466a-97fa-33d353e44b42">
|
65
|
+
<material>
|
66
|
+
<mattext>yellow</mattext>
|
67
|
+
</material>
|
68
|
+
<render_choice>
|
69
|
+
<response_label ident="41dfd716-8fd9-466a-97fa-33d353e44b42-0" scoring_algorithm='TextInChoices' answer_type='openEntry'>
|
70
|
+
<material>
|
71
|
+
<mattext texttype="text/plain">yellow</mattext>
|
72
|
+
</material>
|
73
|
+
</response_label>
|
74
|
+
<response_label ident="41dfd716-8fd9-466a-97fa-33d353e44b42-1" scoring_algorithm='TextInChoices' answer_type='openEntry'>
|
75
|
+
<material>
|
76
|
+
<mattext texttype="text/plain">teal</mattext>
|
77
|
+
</material>
|
78
|
+
</response_label>
|
79
|
+
</render_choice>
|
80
|
+
</response_lid>
|
81
|
+
<response_lid ident="response_a053119f-6a61-4372-ac79-4b2a7de0232f">
|
82
|
+
<material>
|
83
|
+
<mattext>^orange$</mattext>
|
84
|
+
</material>
|
85
|
+
<render_choice>
|
86
|
+
<response_label ident="a053119f-6a61-4372-ac79-4b2a7de0232f-0" scoring_algorithm='TextRegex' answer_type='openEntry'>
|
87
|
+
<material>
|
88
|
+
<mattext texttype="text/plain">^orange$</mattext>
|
89
|
+
</material>
|
90
|
+
</response_label>
|
91
|
+
</render_choice>
|
92
|
+
</response_lid>
|
93
|
+
</presentation>
|
94
|
+
<resprocessing>
|
95
|
+
<outcomes>
|
96
|
+
<decvar maxvalue="100" minvalue="0" varname="SCORE" vartype="Decimal"/>
|
97
|
+
</outcomes>
|
98
|
+
<respcondition>
|
99
|
+
<conditionvar>
|
100
|
+
<varequal respident="response_bafd8242-ecdb-4158-a6d3-4ff15e016cb8">bafd8242-ecdb-4158-a6d3-4ff15e016cb8-0</varequal>
|
101
|
+
</conditionvar>
|
102
|
+
<setvar action="Add" varname="SCORE">20.00</setvar>
|
103
|
+
</respcondition>
|
104
|
+
<respcondition>
|
105
|
+
<conditionvar>
|
106
|
+
<varequal respident="response_d719aee0-6ce0-462c-9c0d-be63ba8d3408">d719aee0-6ce0-462c-9c0d-be63ba8d3408-0</varequal>
|
107
|
+
</conditionvar>
|
108
|
+
<setvar action="Add" varname="SCORE">20.00</setvar>
|
109
|
+
</respcondition>
|
110
|
+
<respcondition>
|
111
|
+
<conditionvar>
|
112
|
+
<varequal respident="response_ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90">ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90-0</varequal>
|
113
|
+
</conditionvar>
|
114
|
+
<setvar action="Add" varname="SCORE">20.00</setvar>
|
115
|
+
</respcondition>
|
116
|
+
<respcondition>
|
117
|
+
<conditionvar>
|
118
|
+
<varequal respident="response_41dfd716-8fd9-466a-97fa-33d353e44b42">41dfd716-8fd9-466a-97fa-33d353e44b42-0</varequal>
|
119
|
+
</conditionvar>
|
120
|
+
<setvar action="Add" varname="SCORE">20.00</setvar>
|
121
|
+
</respcondition>
|
122
|
+
<respcondition>
|
123
|
+
<conditionvar>
|
124
|
+
<varequal respident="response_a053119f-6a61-4372-ac79-4b2a7de0232f">a053119f-6a61-4372-ac79-4b2a7de0232f-0</varequal>
|
125
|
+
</conditionvar>
|
126
|
+
<setvar action="Add" varname="SCORE">20.00</setvar>
|
127
|
+
</respcondition>
|
128
|
+
</resprocessing>
|
129
|
+
</item>
|
130
|
+
</questestinterop>
|
@@ -18,4 +18,18 @@ describe Qti::V1::Models::Choices::FillBlankChoice do
|
|
18
18
|
expect(choices.map(&:item_body).map { |text| text.include? 'correct' }.any?).to eq false
|
19
19
|
end
|
20
20
|
end
|
21
|
+
|
22
|
+
context 'nq_multiple_fib_answer_types.xml' do
|
23
|
+
let(:fixtures_path) { File.join('spec', 'fixtures') }
|
24
|
+
let(:file_path) { File.join(fixtures_path, 'items_1.2', 'nq_multiple_fib_answer_types.xml') }
|
25
|
+
let(:test_object) { Qti::V1::Models::Assessment.from_path!(file_path) }
|
26
|
+
let(:nodes) { test_object.assessment_items }
|
27
|
+
let(:dropdown_answer) { nodes.xpath('.//xmlns:response_lid').first }
|
28
|
+
let(:dropdown_choices) { dropdown_answer.xpath('.//xmlns:response_label') }
|
29
|
+
let(:choices) { dropdown_choices.map { |answer| described_class.new(answer, test_object) } }
|
30
|
+
|
31
|
+
it 'returns the right positions' do
|
32
|
+
expect(choices.map(&:position)).to eq %w[1 2 3]
|
33
|
+
end
|
34
|
+
end
|
21
35
|
end
|
@@ -94,4 +94,20 @@ describe Qti::V1::Models::Interactions::BaseInteraction do
|
|
94
94
|
include_examples('item_level_feedback')
|
95
95
|
include_examples('answer_feedback')
|
96
96
|
end
|
97
|
+
|
98
|
+
context 'nq_multiple_fib_scoring_algorithms.xml' do
|
99
|
+
let(:file_path) { File.join(fixtures_path, 'nq_multiple_fib_scoring_algorithms.xml') }
|
100
|
+
|
101
|
+
it 'correctly finds new quizzes QTI' do
|
102
|
+
expect(Qti::V1::Models::Interactions::BaseInteraction.new_quizzes_fib?(assessment_item_refs.first)).to eq true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'canvas_multiple_fib.xml' do
|
107
|
+
let(:file_path) { File.join(fixtures_path, 'canvas_multiple_fib.xml') }
|
108
|
+
|
109
|
+
it 'correctly does not find new quizzes QTI' do
|
110
|
+
expect(Qti::V1::Models::Interactions::BaseInteraction.new_quizzes_fib?(assessment_item_refs.first)).to eq false
|
111
|
+
end
|
112
|
+
end
|
97
113
|
end
|
@@ -31,6 +31,14 @@ describe Qti::V1::Models::Interactions::FillBlankInteraction do
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
shared_examples_for 'nq_scoring_data_structs' do
|
35
|
+
it 'returns the scoring_data_structs' do
|
36
|
+
expect(loaded_class.scoring_data_structs.map(&:scoring_algorithm)).to eq(scoring_data_algorithm)
|
37
|
+
expect(loaded_class.scoring_data_structs.map(&:answer_type)).to eq(scoring_data_answer_type)
|
38
|
+
expect(loaded_class.scoring_data_structs.map(&:scoring_options)).to eq(scoring_data_options)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
34
42
|
shared_examples_for 'stem_items' do
|
35
43
|
it 'returns the stem_items' do
|
36
44
|
expect(loaded_class.stem_items).to eq(expected_stem_items)
|
@@ -151,6 +159,28 @@ describe Qti::V1::Models::Interactions::FillBlankInteraction do
|
|
151
159
|
allow(subject).to receive(:answer_nodes).and_return([node])
|
152
160
|
expect(subject.scoring_data_structs.first.case).to eq 'no'
|
153
161
|
end
|
162
|
+
|
163
|
+
it "returns 'TextInChoices' as scoring algorithm default value" do
|
164
|
+
allow(nodexml).to receive(:at_xpath)
|
165
|
+
node = double(
|
166
|
+
parent: double(parent: double(attributes: 'a')),
|
167
|
+
content: 'content',
|
168
|
+
attributes: { respident: double(value: 'a') }
|
169
|
+
)
|
170
|
+
allow(subject).to receive(:answer_nodes).and_return([node])
|
171
|
+
expect(subject.scoring_data_structs.first.scoring_algorithm).to eq 'TextInChoices'
|
172
|
+
end
|
173
|
+
|
174
|
+
it "returns 'openEntry' as answer type default value" do
|
175
|
+
allow(nodexml).to receive(:at_xpath)
|
176
|
+
node = double(
|
177
|
+
parent: double(parent: double(attributes: 'a')),
|
178
|
+
content: 'content',
|
179
|
+
attributes: { respident: double(value: 'a') }
|
180
|
+
)
|
181
|
+
allow(subject).to receive(:answer_nodes).and_return([node])
|
182
|
+
expect(subject.scoring_data_structs.first.answer_type).to eq 'openEntry'
|
183
|
+
end
|
154
184
|
end
|
155
185
|
|
156
186
|
context 'fib_str.xml' do
|
@@ -192,22 +222,152 @@ describe Qti::V1::Models::Interactions::FillBlankInteraction do
|
|
192
222
|
let(:scoring_data_case) { %w[no] }
|
193
223
|
let(:answer_count) { 1 }
|
194
224
|
let(:expected_blanks) { [{ id: '3537' }] }
|
195
|
-
let(:expected_stem_items) do
|
196
|
-
[
|
197
|
-
{ id: 'stem_0', position: 1, type: 'text', value: '<div><p>Bird, bird, bird, bird is the ' },
|
198
|
-
{ id: 'stem_1', position: 2, type: 'blank', blank_id: 'response_blank1', blank_name: 'word' },
|
199
|
-
{ id: 'stem_2', position: 3, type: 'text', value: '</p></div>' }
|
200
|
-
]
|
201
|
-
end
|
202
225
|
|
203
226
|
include_examples 'shuffled?'
|
204
227
|
include_examples 'answers'
|
205
228
|
include_examples 'scoring_data_structs'
|
206
229
|
include_examples 'blanks'
|
207
|
-
include_examples 'stem_items'
|
208
230
|
|
209
231
|
it 'returns false for #single_fill_in_blank?' do
|
210
232
|
expect(loaded_class.single_fill_in_blank?).to eq false
|
211
233
|
end
|
212
234
|
end
|
235
|
+
|
236
|
+
context 'new quizzes multiple fill in the blank questions with all possible scoring algorithms' do
|
237
|
+
let(:file_path) { File.join(fixtures_path, 'nq_multiple_fib_scoring_algorithms.xml') }
|
238
|
+
let(:scoring_data_ids) do
|
239
|
+
%w[bafd8242-ecdb-4158-a6d3-4ff15e016cb8-0
|
240
|
+
d719aee0-6ce0-462c-9c0d-be63ba8d3408-0
|
241
|
+
ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90-0
|
242
|
+
41dfd716-8fd9-466a-97fa-33d353e44b42-0
|
243
|
+
41dfd716-8fd9-466a-97fa-33d353e44b42-1
|
244
|
+
a053119f-6a61-4372-ac79-4b2a7de0232f-0]
|
245
|
+
end
|
246
|
+
let(:scoring_data_values) { %w[red blue green yellow teal ^orange$] }
|
247
|
+
let(:scoring_data_case) { %w[no no no no no no] }
|
248
|
+
let(:scoring_data_algorithm) do
|
249
|
+
%w[TextContainsAnswer TextCloseEnough TextEquivalence TextInChoices TextInChoices TextRegex]
|
250
|
+
end
|
251
|
+
let(:scoring_data_answer_type) { %w[openEntry openEntry openEntry openEntry openEntry openEntry] }
|
252
|
+
let(:scoring_data_options) { [{}, { 'ignore_case' => 'true', 'levenshtein_distance' => '1' }, {}, {}, {}, {}] }
|
253
|
+
let(:correct_answer_map) do
|
254
|
+
{ 'response_bafd8242-ecdb-4158-a6d3-4ff15e016cb8' => 'bafd8242-ecdb-4158-a6d3-4ff15e016cb8-0',
|
255
|
+
'response_d719aee0-6ce0-462c-9c0d-be63ba8d3408' => 'd719aee0-6ce0-462c-9c0d-be63ba8d3408-0',
|
256
|
+
'response_ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90' => 'ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90-0',
|
257
|
+
'response_41dfd716-8fd9-466a-97fa-33d353e44b42' => '41dfd716-8fd9-466a-97fa-33d353e44b42-0',
|
258
|
+
'response_a053119f-6a61-4372-ac79-4b2a7de0232f' => 'a053119f-6a61-4372-ac79-4b2a7de0232f-0' }
|
259
|
+
end
|
260
|
+
let(:expected_stem_items) do
|
261
|
+
[{ id: 'stem_0', position: 1, type: 'text', value: '<p>Roses are ' },
|
262
|
+
{ blank_id: 'response_bafd8242-ecdb-4158-a6d3-4ff15e016cb8',
|
263
|
+
blank_name: 'red',
|
264
|
+
id: 'stem_1',
|
265
|
+
position: 2,
|
266
|
+
type: 'blank' },
|
267
|
+
{ id: 'stem_2', position: 3, type: 'text', value: ', ' },
|
268
|
+
{ blank_id: 'response_d719aee0-6ce0-462c-9c0d-be63ba8d3408',
|
269
|
+
blank_name: 'blue',
|
270
|
+
id: 'stem_3',
|
271
|
+
position: 4,
|
272
|
+
type: 'blank' },
|
273
|
+
{ id: 'stem_4', position: 5, type: 'text', value: ', ' },
|
274
|
+
{ blank_id: 'response_ce47aaf6-c1dd-4db8-9a9f-7f5c131d8a90',
|
275
|
+
blank_name: 'green',
|
276
|
+
id: 'stem_5',
|
277
|
+
position: 6,
|
278
|
+
type: 'blank' },
|
279
|
+
{ id: 'stem_6', position: 7, type: 'text', value: ', ' },
|
280
|
+
{ blank_id: 'response_41dfd716-8fd9-466a-97fa-33d353e44b42',
|
281
|
+
blank_name: 'yellow',
|
282
|
+
id: 'stem_7',
|
283
|
+
position: 8,
|
284
|
+
type: 'blank' },
|
285
|
+
{ id: 'stem_8', position: 9, type: 'text', value: ', and ' },
|
286
|
+
{ blank_id: 'response_a053119f-6a61-4372-ac79-4b2a7de0232f',
|
287
|
+
blank_name: '^orange$',
|
288
|
+
id: 'stem_9',
|
289
|
+
position: 10,
|
290
|
+
type: 'blank' },
|
291
|
+
{ id: 'stem_10', position: 11, type: 'text', value: '.</p>' }]
|
292
|
+
end
|
293
|
+
|
294
|
+
include_examples 'scoring_data_structs'
|
295
|
+
include_examples 'nq_scoring_data_structs'
|
296
|
+
include_examples 'stem_items'
|
297
|
+
|
298
|
+
it 'returns false for #wordbank_answer_present?' do
|
299
|
+
expect(loaded_class.wordbank_answer_present?).to eq false
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'returns nil for #wordbank_allow_reuse?' do
|
303
|
+
expect(loaded_class.wordbank_allow_reuse?).to eq nil
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'returns nil for #wordbank_choices' do
|
307
|
+
expect(loaded_class.wordbank_choices).to eq nil
|
308
|
+
end
|
309
|
+
|
310
|
+
it 'returns the answer map for #correct_answer_map' do
|
311
|
+
expect(loaded_class.correct_answer_map).to eq(correct_answer_map)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
context 'new quizzes multiple fill in the blank questions with all possible answer types' do
|
316
|
+
let(:file_path) { File.join(fixtures_path, 'nq_multiple_fib_answer_types.xml') }
|
317
|
+
let(:scoring_data_ids) do
|
318
|
+
%w[378646a8-b823-4e5b-beb8-19ca63f1f355
|
319
|
+
c9400bfb-78ea-45b6-b5a5-e3311f6d5ed0
|
320
|
+
76350749-49ac-4094-966b-c4e1f12d54bc
|
321
|
+
cd11d826-906d-4dc4-b78d-d66516eb94ce
|
322
|
+
bae0bd4f-2327-4a3e-b29f-199e1e279e91]
|
323
|
+
end
|
324
|
+
let(:scoring_data_values) { %w[black violet grey brown white] }
|
325
|
+
let(:scoring_data_case) { %w[no no no no no] }
|
326
|
+
let(:scoring_data_algorithm) { %w[Equivalence Equivalence Equivalence TextEquivalence TextEquivalence] }
|
327
|
+
let(:scoring_data_answer_type) { %w[dropdown dropdown dropdown wordbank wordbank] }
|
328
|
+
let(:scoring_data_options) { [{}, {}, {}, { 'allow_reuse' => 'true' }, { 'allow_reuse' => 'true' }] }
|
329
|
+
let(:wordbank_choices) do
|
330
|
+
[{ id: 'cd11d826-906d-4dc4-b78d-d66516eb94ce', item_body: 'brown' },
|
331
|
+
{ id: 'bae0bd4f-2327-4a3e-b29f-199e1e279e91', item_body: 'white' }]
|
332
|
+
end
|
333
|
+
let(:correct_answer_map) do
|
334
|
+
{ 'response_a20629ed-0c0b-4959-b565-a80c0f199602' => '378646a8-b823-4e5b-beb8-19ca63f1f355',
|
335
|
+
'response_ab37a945-ebad-4787-a356-66c3c91efcc6' => 'bae0bd4f-2327-4a3e-b29f-199e1e279e91' }
|
336
|
+
end
|
337
|
+
let(:expected_stem_items) do
|
338
|
+
[{ id: 'stem_0', position: 1, type: 'text', value: '<p>Roses are ' },
|
339
|
+
{ blank_id: 'response_a20629ed-0c0b-4959-b565-a80c0f199602',
|
340
|
+
blank_name: 'black',
|
341
|
+
id: 'stem_1',
|
342
|
+
position: 2,
|
343
|
+
type: 'blank' },
|
344
|
+
{ id: 'stem_2', position: 3, type: 'text', value: ' and ' },
|
345
|
+
{ blank_id: 'response_ab37a945-ebad-4787-a356-66c3c91efcc6',
|
346
|
+
blank_name: 'white',
|
347
|
+
id: 'stem_3',
|
348
|
+
position: 4,
|
349
|
+
type: 'blank' },
|
350
|
+
{ id: 'stem_4', position: 5, type: 'text', value: '.</p>' }]
|
351
|
+
end
|
352
|
+
|
353
|
+
include_examples 'scoring_data_structs'
|
354
|
+
include_examples 'nq_scoring_data_structs'
|
355
|
+
include_examples 'stem_items'
|
356
|
+
|
357
|
+
it 'returns true for #wordbank_answer_present?' do
|
358
|
+
expect(loaded_class.wordbank_answer_present?).to eq true
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'returns true for #wordbank_allow_reuse?' do
|
362
|
+
expect(loaded_class.wordbank_allow_reuse?).to eq true
|
363
|
+
end
|
364
|
+
|
365
|
+
it 'returns the correct choices for #wordbank_choices' do
|
366
|
+
expect(loaded_class.wordbank_choices).to eq(wordbank_choices)
|
367
|
+
end
|
368
|
+
|
369
|
+
it 'returns the answer map for #correct_answer_map' do
|
370
|
+
expect(loaded_class.correct_answer_map).to eq(correct_answer_map)
|
371
|
+
end
|
372
|
+
end
|
213
373
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qti
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.20.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adrian Diaz
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2023-08-
|
15
|
+
date: 2023-08-03 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: actionview
|
@@ -417,6 +417,8 @@ files:
|
|
417
417
|
- spec/fixtures/items_1.2/multiple_answer.xml
|
418
418
|
- spec/fixtures/items_1.2/multiple_answer_canvas.xml
|
419
419
|
- spec/fixtures/items_1.2/multiple_choice.xml
|
420
|
+
- spec/fixtures/items_1.2/nq_multiple_fib_answer_types.xml
|
421
|
+
- spec/fixtures/items_1.2/nq_multiple_fib_scoring_algorithms.xml
|
420
422
|
- spec/fixtures/items_1.2/numeric_exact_match.xml
|
421
423
|
- spec/fixtures/items_1.2/numeric_margin_error.xml
|
422
424
|
- spec/fixtures/items_1.2/numeric_margin_error_percent.xml
|
@@ -789,6 +791,8 @@ test_files:
|
|
789
791
|
- spec/fixtures/items_1.2/multiple_answer.xml
|
790
792
|
- spec/fixtures/items_1.2/multiple_answer_canvas.xml
|
791
793
|
- spec/fixtures/items_1.2/multiple_choice.xml
|
794
|
+
- spec/fixtures/items_1.2/nq_multiple_fib_answer_types.xml
|
795
|
+
- spec/fixtures/items_1.2/nq_multiple_fib_scoring_algorithms.xml
|
792
796
|
- spec/fixtures/items_1.2/numeric_exact_match.xml
|
793
797
|
- spec/fixtures/items_1.2/numeric_margin_error.xml
|
794
798
|
- spec/fixtures/items_1.2/numeric_margin_error_percent.xml
|