qti 2.16.0 → 2.17.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 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