qti 2.16.0 → 2.17.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c06cc4e78f29ecb23354e39dc3e93ac5da9600f0b09284a59b1202c553eb0c93
4
- data.tar.gz: b91ded981174a7822c7b6756f69e4a30daf5c6845f004a9a05da4a3f23a6af28
3
+ metadata.gz: 8ecae7fca00b12644bc2942b43f7fe47c6b959823c6b4b3a92e70193529b064f
4
+ data.tar.gz: b9c35c2873033a84e0b673b58e4859d434de0ea0052a4891b38e448de034759d
5
5
  SHA512:
6
- metadata.gz: afcb1b453f47cec30c61b8d56d0a80109b6660a9f71b1e863db23cf8a3f6f79095dffd1512dad87554ba316d512574391dfe1f87da8c4bfa4efbbe74f95fcd2f
7
- data.tar.gz: a5acbfc53c6508b9d50e795435476b9aa72e32390f739697fd319dc4e4777037db4f7af5b70acbdc698fadcc5e0251af22e2ea7907cbf5b5ee8a3bed298a0b4b
6
+ metadata.gz: c7ec25c08693f1b467121f8d528859c99ff58d57ad9eb55d44fb31871210a6d04f0184517db425ce62509ab3ea00700ea6203e1d4ae3323de4b4eaad3577f0a1
7
+ data.tar.gz: aa5a807baa86a4554c52a83c0d2c16c7421c82ef1bb5a4c680cfc1e2f4cb9c5bbcea79936d1bc02984fb0bb74d6c4b6d44a8d1bdc0b8d7f4272f49fc27249018
@@ -0,0 +1,81 @@
1
+ module Qti
2
+ module V1
3
+ module Models
4
+ module Interactions
5
+ class CategorizationInteraction < BaseInteraction
6
+ def self.matches(node, parent)
7
+ return false if canvas_multiple_fib?(node)
8
+ matches = node.xpath('.//xmlns:response_lid')
9
+ return false if matches.count <= 1
10
+ rcardinality = matches.first.attributes['rcardinality']&.value || 'Single'
11
+ return false if rcardinality != 'Multiple'
12
+ new(node, parent)
13
+ end
14
+
15
+ def item_body
16
+ @item_body ||= begin
17
+ node = @node.dup
18
+ presentation = node.at_xpath('.//xmlns:presentation')
19
+ mattext = presentation.at_xpath('.//xmlns:mattext')
20
+ inner_content = return_inner_content!(mattext)
21
+ sanitize_content!(inner_content)
22
+ end
23
+ end
24
+
25
+ def categories
26
+ node.xpath('.//xmlns:response_lid').map do |lid_node|
27
+ mattext = lid_node.at_xpath('.//xmlns:mattext')
28
+ inner_content = return_inner_content!(mattext)
29
+ item_body = sanitize_content!(inner_content)
30
+ category_id = lid_node.attributes['ident'].value
31
+ [category_id, { id: category_id, item_body: item_body }]
32
+ end.to_h
33
+ end
34
+
35
+ def distractors
36
+ @distractors ||= choices.reduce({}) do |acc, answer|
37
+ acc.update answer.identifier => answer.item_body
38
+ end
39
+ end
40
+
41
+ def scoring_data_structs
42
+ @scoring_data_structs ||= parse_scoring_data
43
+ end
44
+
45
+ private
46
+
47
+ def parse_scoring_data
48
+ # This preserves the original behavior while not breaking on item feedback
49
+ path = './/xmlns:respcondition/xmlns:setvar/../xmlns:conditionvar/xmlns:varequal'
50
+ raw_scoring_data = node.xpath(path).reduce({}) do |acc, node|
51
+ category_id = node.attributes['respident'].value
52
+ values = (acc[category_id] || []) + [node.text]
53
+ acc.update category_id => values
54
+ end
55
+
56
+ raw_scoring_data.map do |id, values|
57
+ Models::ScoringData.new(values, rcardinality, id: id)
58
+ end
59
+ end
60
+
61
+ def choices
62
+ @choices ||= answer_nodes.map do |node|
63
+ V1::Models::Choices::LogicalIdentifierChoice.new(node, self)
64
+ end
65
+ end
66
+
67
+ def answer_nodes
68
+ responses = []
69
+ response_ids = Set.new
70
+ node.xpath('.//xmlns:response_label').each do |answer_node|
71
+ ident = answer_node.attributes['ident'].value
72
+ responses << answer_node unless response_ids.include? ident
73
+ response_ids << ident
74
+ end
75
+ responses
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -17,7 +17,8 @@ module Qti
17
17
  'calculated_question' => FormulaInteraction,
18
18
  'essay_question ' => StringInteraction,
19
19
  'file_upload_question' => UploadInteraction,
20
- 'hot_spot_question' => HotSpotInteraction
20
+ 'hot_spot_question' => HotSpotInteraction,
21
+ 'categorization_question' => CategorizationInteraction
21
22
  # "text_only_question" => StimulusNoQuestion
22
23
  }.freeze
23
24
 
data/lib/qti/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Qti
2
- VERSION = '2.16.0'.freeze
2
+ VERSION = '2.17.0'.freeze
3
3
  end
data/lib/qti.rb CHANGED
@@ -123,6 +123,7 @@ require 'qti/v1/models/interactions/choice_interaction'
123
123
 
124
124
  require 'qti/v1/models/interactions/base_fill_blank_interaction'
125
125
  require 'qti/v1/models/interactions/canvas_multiple_dropdown'
126
+ require 'qti/v1/models/interactions/categorization_interaction'
126
127
  require 'qti/v1/models/interactions/fill_blank_interaction'
127
128
  require 'qti/v1/models/interactions/formula_interaction'
128
129
  require 'qti/v1/models/interactions/hot_spot_interaction'
@@ -0,0 +1,133 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
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
+ <assessment ident="id2b817165c302c0c57416146dc94ce47" title="Quiz with hot-spot question">
4
+ <qtimetadata>
5
+ <qtimetadatafield>
6
+ <fieldlabel>cc_maxattempts</fieldlabel>
7
+ <fieldentry>1</fieldentry>
8
+ </qtimetadatafield>
9
+ </qtimetadata>
10
+ <section ident="root_section">
11
+ <item ident="d74b47a4abeeed159e030bfecbcecea2" title="quiz with categorization question">
12
+ <itemmetadata>
13
+ <qtimetadata>
14
+ <qtimetadatafield>
15
+ <fieldlabel>question_type</fieldlabel>
16
+ <fieldentry>categorization_question</fieldentry>
17
+ </qtimetadatafield>
18
+ <qtimetadatafield>
19
+ <fieldlabel>points_possible</fieldlabel>
20
+ <fieldentry>1.0</fieldentry>
21
+ </qtimetadatafield>
22
+ <qtimetadatafield>
23
+ <fieldlabel>original_answer_ids</fieldlabel>
24
+ <fieldentry/>
25
+ </qtimetadatafield>
26
+ <qtimetadatafield>
27
+ <fieldlabel>assessment_question_identifierref</fieldlabel>
28
+ <fieldentry>9f47365e112840d80a79a960b1b0b6a7</fieldentry>
29
+ </qtimetadatafield>
30
+ </qtimetadata>
31
+ </itemmetadata>
32
+ <presentation>
33
+ <material>
34
+ <mattext texttype="text/html">&lt;p&gt;Hello there&lt;/p&gt;</mattext>
35
+ </material>
36
+ <response_lid ident="df4fb8d7-8033-445a-b8be-171dd9b973ba" rcardinality="Multiple">
37
+ <material>
38
+ <mattext texttype="text/plain">Category_A</mattext>
39
+ </material>
40
+ <render_choice>
41
+ <response_label ident="19e37ce3-3a8b-4e26-ab61-9770e58af00e">
42
+ <material>
43
+ <mattext texttype="text/plain">Distractor2</mattext>
44
+ </material>
45
+ </response_label>
46
+ <response_label ident="3b8dcef8-c8c1-4827-85b0-6e381739adbf">
47
+ <material>
48
+ <mattext texttype="text/plain">B1</mattext>
49
+ </material>
50
+ </response_label>
51
+ <response_label ident="53a97aa7-d55c-402c-a061-f85d837c7c77">
52
+ <material>
53
+ <mattext texttype="text/plain">A2</mattext>
54
+ </material>
55
+ </response_label>
56
+ <response_label ident="5a67c627-cf85-4b70-922a-c7d1b7c720b8">
57
+ <material>
58
+ <mattext texttype="text/plain">B2</mattext>
59
+ </material>
60
+ </response_label>
61
+ <response_label ident="5d78f3f5-28c2-4e71-9c33-70c6b301e017">
62
+ <material>
63
+ <mattext texttype="text/plain">Distractor1</mattext>
64
+ </material>
65
+ </response_label>
66
+ <response_label ident="9432e8cb-1d3e-4d04-89b5-95185b7e557d">
67
+ <material>
68
+ <mattext texttype="text/plain">A1</mattext>
69
+ </material>
70
+ </response_label>
71
+ </render_choice>
72
+ </response_lid>
73
+ <response_lid ident="eb2f823d-b857-4c06-9d5f-998d14ee8d58" rcardinality="Multiple">
74
+ <material>
75
+ <mattext texttype="text/plain">Category_B</mattext>
76
+ </material>
77
+ <render_choice>
78
+ <response_label ident="19e37ce3-3a8b-4e26-ab61-9770e58af00e">
79
+ <material>
80
+ <mattext texttype="text/plain">Distractor2</mattext>
81
+ </material>
82
+ </response_label>
83
+ <response_label ident="3b8dcef8-c8c1-4827-85b0-6e381739adbf">
84
+ <material>
85
+ <mattext texttype="text/plain">B1</mattext>
86
+ </material>
87
+ </response_label>
88
+ <response_label ident="53a97aa7-d55c-402c-a061-f85d837c7c77">
89
+ <material>
90
+ <mattext texttype="text/plain">A2</mattext>
91
+ </material>
92
+ </response_label>
93
+ <response_label ident="5a67c627-cf85-4b70-922a-c7d1b7c720b8">
94
+ <material>
95
+ <mattext texttype="text/plain">B2</mattext>
96
+ </material>
97
+ </response_label>
98
+ <response_label ident="5d78f3f5-28c2-4e71-9c33-70c6b301e017">
99
+ <material>
100
+ <mattext texttype="text/plain">Distractor1</mattext>
101
+ </material>
102
+ </response_label>
103
+ <response_label ident="9432e8cb-1d3e-4d04-89b5-95185b7e557d">
104
+ <material>
105
+ <mattext texttype="text/plain">A1</mattext>
106
+ </material>
107
+ </response_label>
108
+ </render_choice>
109
+ </response_lid>
110
+ </presentation>
111
+ <resprocessing>
112
+ <outcomes>
113
+ <decvar maxvalue="100" minvalue="0" varname="SCORE" vartype="Decimal"/>
114
+ </outcomes>
115
+ <respcondition>
116
+ <conditionvar>
117
+ <varequal respident="df4fb8d7-8033-445a-b8be-171dd9b973ba">9432e8cb-1d3e-4d04-89b5-95185b7e557d</varequal>
118
+ <varequal respident="df4fb8d7-8033-445a-b8be-171dd9b973ba">53a97aa7-d55c-402c-a061-f85d837c7c77</varequal>
119
+ </conditionvar>
120
+ <setvar action="Add" varname="SCORE">50.00</setvar>
121
+ </respcondition>
122
+ <respcondition>
123
+ <conditionvar>
124
+ <varequal respident="eb2f823d-b857-4c06-9d5f-998d14ee8d58">3b8dcef8-c8c1-4827-85b0-6e381739adbf</varequal>
125
+ <varequal respident="eb2f823d-b857-4c06-9d5f-998d14ee8d58">5a67c627-cf85-4b70-922a-c7d1b7c720b8</varequal>
126
+ </conditionvar>
127
+ <setvar action="Add" varname="SCORE">50.00</setvar>
128
+ </respcondition>
129
+ </resprocessing>
130
+ </item>
131
+ </section>
132
+ </assessment>
133
+ </questestinterop>
@@ -0,0 +1,62 @@
1
+ describe Qti::V1::Models::Interactions::CategorizationInteraction do
2
+ let(:xml_file_name) { 'categorization.xml' }
3
+ let(:fixtures_path) { File.join('spec', 'fixtures', 'items_1.2') }
4
+ let(:file_path) { File.join(fixtures_path, xml_file_name) }
5
+ let(:test_object) { Qti::V1::Models::Assessment.from_path!(file_path) }
6
+ let(:assessment_item_refs) { test_object.assessment_items }
7
+ let(:item) { assessment_item_refs.first }
8
+
9
+ describe '.matches' do
10
+ it 'matches the item in file' do
11
+ expect(described_class.matches(item, test_object)).to be_truthy
12
+ end
13
+ end
14
+
15
+ describe '#item_body' do
16
+ let(:loaded_class) { described_class.new(item, test_object) }
17
+ it 'returns correct item body' do
18
+ expect(loaded_class.item_body).to eq '<p>Hello there</p>'
19
+ end
20
+ end
21
+
22
+ describe '#categories' do
23
+ let(:loaded_class) { described_class.new(item, test_object) }
24
+ it 'returns correct categories' do
25
+ expected_categories =
26
+ {
27
+ 'df4fb8d7-8033-445a-b8be-171dd9b973ba' => { id: 'df4fb8d7-8033-445a-b8be-171dd9b973ba',
28
+ item_body: 'Category_A' },
29
+ 'eb2f823d-b857-4c06-9d5f-998d14ee8d58' => { id: 'eb2f823d-b857-4c06-9d5f-998d14ee8d58',
30
+ item_body: 'Category_B' }
31
+ }
32
+ expect(loaded_class.categories).to eq(expected_categories)
33
+ end
34
+ end
35
+
36
+ describe '#distractors' do
37
+ let(:loaded_class) { described_class.new(item, test_object) }
38
+ it 'returns the correct distractors' do
39
+ expected_distractors =
40
+ { '19e37ce3-3a8b-4e26-ab61-9770e58af00e' => 'Distractor2',
41
+ '3b8dcef8-c8c1-4827-85b0-6e381739adbf' => 'B1',
42
+ '53a97aa7-d55c-402c-a061-f85d837c7c77' => 'A2',
43
+ '5a67c627-cf85-4b70-922a-c7d1b7c720b8' => 'B2',
44
+ '5d78f3f5-28c2-4e71-9c33-70c6b301e017' => 'Distractor1',
45
+ '9432e8cb-1d3e-4d04-89b5-95185b7e557d' => 'A1' }
46
+ expect(loaded_class.distractors).to eq(expected_distractors)
47
+ end
48
+ end
49
+
50
+ describe '#scoring_data_structs' do
51
+ let(:loaded_class) { described_class.new(item, test_object) }
52
+ it 'returns the correct scoring data structs' do
53
+ scoring_data_ids = %w[df4fb8d7-8033-445a-b8be-171dd9b973ba eb2f823d-b857-4c06-9d5f-998d14ee8d58]
54
+ scoring_data_values = [
55
+ %w[9432e8cb-1d3e-4d04-89b5-95185b7e557d 53a97aa7-d55c-402c-a061-f85d837c7c77],
56
+ %w[3b8dcef8-c8c1-4827-85b0-6e381739adbf 5a67c627-cf85-4b70-922a-c7d1b7c720b8]
57
+ ]
58
+ expect(loaded_class.scoring_data_structs.map(&:id)).to eq(scoring_data_ids)
59
+ expect(loaded_class.scoring_data_structs.map(&:values)).to eq(scoring_data_values)
60
+ end
61
+ end
62
+ 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.16.0
4
+ version: 2.17.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-07-06 00:00:00.000000000 Z
15
+ date: 2023-07-10 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: actionview
@@ -332,6 +332,7 @@ files:
332
332
  - lib/qti/v1/models/interactions/base_fill_blank_interaction.rb
333
333
  - lib/qti/v1/models/interactions/base_interaction.rb
334
334
  - lib/qti/v1/models/interactions/canvas_multiple_dropdown.rb
335
+ - lib/qti/v1/models/interactions/categorization_interaction.rb
335
336
  - lib/qti/v1/models/interactions/choice_interaction.rb
336
337
  - lib/qti/v1/models/interactions/fill_blank_interaction.rb
337
338
  - lib/qti/v1/models/interactions/formula_interaction.rb
@@ -397,6 +398,7 @@ files:
397
398
  - spec/fixtures/items_1.2/canvas_multiple_fib.xml
398
399
  - spec/fixtures/items_1.2/canvas_multiple_fib_as_single.xml
399
400
  - spec/fixtures/items_1.2/canvas_multiple_fib_extra_brackets.xml
401
+ - spec/fixtures/items_1.2/categorization.xml
400
402
  - spec/fixtures/items_1.2/choice.xml
401
403
  - spec/fixtures/items_1.2/essay.xml
402
404
  - spec/fixtures/items_1.2/fib.xml
@@ -678,6 +680,7 @@ files:
678
680
  - spec/lib/qti/v1/models/interactions/base_fill_blank_interaction_spec.rb
679
681
  - spec/lib/qti/v1/models/interactions/base_interaction_spec.rb
680
682
  - spec/lib/qti/v1/models/interactions/canvas_multiple_dropdown_spec.rb
683
+ - spec/lib/qti/v1/models/interactions/categorization_interaction_spec.rb
681
684
  - spec/lib/qti/v1/models/interactions/choice_interaction_spec.rb
682
685
  - spec/lib/qti/v1/models/interactions/fill_blank_interaction_spec.rb
683
686
  - spec/lib/qti/v1/models/interactions/formula_interaction_spec.rb
@@ -762,6 +765,7 @@ test_files:
762
765
  - spec/fixtures/items_1.2/canvas_multiple_fib.xml
763
766
  - spec/fixtures/items_1.2/canvas_multiple_fib_as_single.xml
764
767
  - spec/fixtures/items_1.2/canvas_multiple_fib_extra_brackets.xml
768
+ - spec/fixtures/items_1.2/categorization.xml
765
769
  - spec/fixtures/items_1.2/choice.xml
766
770
  - spec/fixtures/items_1.2/essay.xml
767
771
  - spec/fixtures/items_1.2/fib.xml
@@ -1043,6 +1047,7 @@ test_files:
1043
1047
  - spec/lib/qti/v1/models/interactions/base_fill_blank_interaction_spec.rb
1044
1048
  - spec/lib/qti/v1/models/interactions/base_interaction_spec.rb
1045
1049
  - spec/lib/qti/v1/models/interactions/canvas_multiple_dropdown_spec.rb
1050
+ - spec/lib/qti/v1/models/interactions/categorization_interaction_spec.rb
1046
1051
  - spec/lib/qti/v1/models/interactions/choice_interaction_spec.rb
1047
1052
  - spec/lib/qti/v1/models/interactions/fill_blank_interaction_spec.rb
1048
1053
  - spec/lib/qti/v1/models/interactions/formula_interaction_spec.rb