qti 0.9.8 → 0.9.9
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.rb +7 -0
- data/lib/qti/v1/models/interactions.rb +0 -1
- data/lib/qti/v1/models/interactions/numeric_interaction.rb +4 -14
- data/lib/qti/v1/models/numerics/exact_match.rb +43 -0
- data/lib/qti/v1/models/numerics/margin_error.rb +51 -0
- data/lib/qti/v1/models/numerics/precision.rb +65 -0
- data/lib/qti/v1/models/numerics/scoring_base.rb +29 -0
- data/lib/qti/v1/models/numerics/scoring_data.rb +26 -0
- data/lib/qti/v1/models/numerics/scoring_node.rb +29 -0
- data/lib/qti/v1/models/numerics/within_range.rb +36 -0
- data/lib/qti/version.rb +1 -1
- data/spec/fixtures/items_1.2/{numeric.xml → numeric_exact_match.xml} +0 -0
- data/spec/fixtures/items_1.2/numeric_margin_error.xml +58 -0
- data/spec/fixtures/items_1.2/numeric_precision.xml +58 -0
- data/spec/fixtures/items_1.2/numeric_within_range.xml +53 -0
- data/spec/gemfiles/nokogiri-1.8.gemfile.lock +35 -18
- data/spec/lib/qti/v1/models/interactions/numeric_interaction_spec.rb +92 -27
- data/spec/lib/qti/v1/models/numerics/exact_match_spec.rb +84 -0
- data/spec/lib/qti/v1/models/numerics/margin_error_spec.rb +80 -0
- data/spec/lib/qti/v1/models/numerics/precision_spec.rb +88 -0
- data/spec/lib/qti/v1/models/numerics/scoring_base_spec.rb +35 -0
- data/spec/lib/qti/v1/models/numerics/scoring_node_spec.rb +29 -0
- data/spec/lib/qti/v1/models/numerics/within_range_spec.rb +64 -0
- data/spec/spec_helper.rb +1 -0
- metadata +44 -17
- data/spec/gemfiles/nokogiri-1.6.gemfile.lock +0 -127
- data/spec/gemfiles/rails-4.2.gemfile.lock +0 -204
- data/spec/gemfiles/rails-5.0.gemfile.lock +0 -210
- data/spec/gemfiles/rails-5.1.gemfile.lock +0 -210
- data/spec/gemfiles/sanitize-4.2.gemfile.lock +0 -127
- data/spec/gemfiles/sanitize-4.5.gemfile.lock +0 -127
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8fb6165196a5601ea5d6bb415d14897246d6ef7
|
4
|
+
data.tar.gz: a30a49e90c67b24c0762a01c76688520f1633f7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4dfa4a3312e1a88ec6a37a5282aff21195a624348567ec92c5b9b4798ddfd06deeb6ad67a9b79547299b2b4abe27c602b2d1a74fe48c98af2e65a57802f58b9c
|
7
|
+
data.tar.gz: 3f8f93715a6de83ced239389ff5a58bbbd322f93b1a9a2f53abf72616f922481befa180a08b2b970bae4aea8254b155bace0d16c36b2c34e043d5ae4103d59a0
|
data/lib/qti.rb
CHANGED
@@ -53,6 +53,13 @@ require 'qti/v1/models/choices/logical_identifier_choice'
|
|
53
53
|
require 'qti/v1/models/choices/fill_blank_choice'
|
54
54
|
require 'qti/v1/models/scoring_data'
|
55
55
|
|
56
|
+
require 'qti/v1/models/numerics/exact_match'
|
57
|
+
require 'qti/v1/models/numerics/margin_error'
|
58
|
+
require 'qti/v1/models/numerics/precision'
|
59
|
+
require 'qti/v1/models/numerics/scoring_data'
|
60
|
+
require 'qti/v1/models/numerics/scoring_node'
|
61
|
+
require 'qti/v1/models/numerics/within_range'
|
62
|
+
|
56
63
|
require 'qti/v2/models/base'
|
57
64
|
require 'qti/v2/models/choices/simple_choice'
|
58
65
|
require 'qti/v2/models/choices/simple_associable_choice'
|
@@ -8,7 +8,6 @@ module Qti
|
|
8
8
|
# This one finds the correct parsing model based on the provided xml node
|
9
9
|
def self.interaction_model(node, parent)
|
10
10
|
subclasses = constants.map { |c| const_get(c) }
|
11
|
-
|
12
11
|
# Check for matches
|
13
12
|
matches = subclasses.each_with_object([]) do |interaction_class, result|
|
14
13
|
match = interaction_class.matches(node, parent)
|
@@ -7,8 +7,6 @@ module Qti
|
|
7
7
|
def self.matches(node, parent)
|
8
8
|
first_match = node.at_xpath('.//xmlns:render_fib')
|
9
9
|
return false unless first_match && first_match.attributes['fibtype']&.value == 'Decimal'
|
10
|
-
# exclude types other than "Exact response"
|
11
|
-
return false unless node.at_xpath('.//xmlns:varequal').present?
|
12
10
|
return false if node.xpath('.//xmlns:render_fib').count > 1
|
13
11
|
new(node, parent)
|
14
12
|
end
|
@@ -23,26 +21,18 @@ module Qti
|
|
23
21
|
end
|
24
22
|
end
|
25
23
|
|
26
|
-
def answers
|
27
|
-
@answers ||= answer_nodes.map do |node|
|
28
|
-
V1::Models::Choices::FillBlankChoice.new(node, self)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
24
|
def scoring_data_structs
|
33
25
|
answer_nodes.map do |value_node|
|
34
|
-
ScoringData.new(
|
35
|
-
value_node
|
36
|
-
|
37
|
-
id: value_node.attributes['respident']&.value
|
38
|
-
)
|
26
|
+
V1::Models::Numerics::ScoringData.new(
|
27
|
+
value_node
|
28
|
+
).scoring_data
|
39
29
|
end
|
40
30
|
end
|
41
31
|
|
42
32
|
private
|
43
33
|
|
44
34
|
def answer_nodes
|
45
|
-
@node.xpath('.//xmlns:
|
35
|
+
@node.xpath('.//xmlns:respcondition')
|
46
36
|
end
|
47
37
|
end
|
48
38
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'qti/v1/models/numerics/scoring_base'
|
2
|
+
|
3
|
+
module Qti
|
4
|
+
module V1
|
5
|
+
module Models
|
6
|
+
module Numerics
|
7
|
+
class ExactMatch < ScoringBase
|
8
|
+
def initialize(scoring_node)
|
9
|
+
super(scoring_node)
|
10
|
+
end
|
11
|
+
|
12
|
+
def scoring_data
|
13
|
+
return unless valid?
|
14
|
+
Struct.new(
|
15
|
+
:id,
|
16
|
+
:type,
|
17
|
+
:value
|
18
|
+
).new(
|
19
|
+
equal_node.attributes['respident']&.value,
|
20
|
+
'exactResponse',
|
21
|
+
equal_node.content
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def valid?
|
28
|
+
node_complete? && content_consistent?
|
29
|
+
end
|
30
|
+
|
31
|
+
def node_complete?
|
32
|
+
(equal_node && gte_node && lte_node).present?
|
33
|
+
end
|
34
|
+
|
35
|
+
def content_consistent?
|
36
|
+
equal_node.content == gte_node.content &&
|
37
|
+
equal_node.content == lte_node.content
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'qti/v1/models/numerics/scoring_base'
|
2
|
+
|
3
|
+
module Qti
|
4
|
+
module V1
|
5
|
+
module Models
|
6
|
+
module Numerics
|
7
|
+
class MarginError < ScoringBase
|
8
|
+
def initialize(scoring_node)
|
9
|
+
super(scoring_node)
|
10
|
+
end
|
11
|
+
|
12
|
+
def scoring_data
|
13
|
+
return unless valid?
|
14
|
+
Struct.new(
|
15
|
+
:id,
|
16
|
+
:type,
|
17
|
+
:value,
|
18
|
+
:margin,
|
19
|
+
:margin_type
|
20
|
+
).new(
|
21
|
+
equal_node.attributes['respident']&.value,
|
22
|
+
'marginOfError',
|
23
|
+
equal_node.content.to_f.to_s,
|
24
|
+
margin,
|
25
|
+
'absolute'
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def margin
|
32
|
+
(equal_node.content.to_f - gte_node.content.to_f).to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def valid?
|
36
|
+
node_complete? && content_consistent?
|
37
|
+
end
|
38
|
+
|
39
|
+
def node_complete?
|
40
|
+
(equal_node && gte_node && lte_node).present?
|
41
|
+
end
|
42
|
+
|
43
|
+
def content_consistent?
|
44
|
+
equal_node.content != gte_node.content &&
|
45
|
+
equal_node.content != lte_node.content
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'qti/v1/models/numerics/scoring_base'
|
2
|
+
|
3
|
+
module Qti
|
4
|
+
module V1
|
5
|
+
module Models
|
6
|
+
module Numerics
|
7
|
+
class Precision < ScoringBase
|
8
|
+
include ActionView::Helpers::NumberHelper
|
9
|
+
|
10
|
+
def initialize(scoring_node)
|
11
|
+
super(scoring_node)
|
12
|
+
end
|
13
|
+
|
14
|
+
def scoring_data
|
15
|
+
return unless equal_node && gt_node && lte_node
|
16
|
+
Struct.new(
|
17
|
+
:id,
|
18
|
+
:type,
|
19
|
+
:value,
|
20
|
+
:precision,
|
21
|
+
:precision_type
|
22
|
+
).new(
|
23
|
+
equal_node.attributes['respident']&.value,
|
24
|
+
'preciseResponse',
|
25
|
+
value,
|
26
|
+
precision.to_s,
|
27
|
+
'significantDigits'
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def value
|
32
|
+
fval = equal_node.content.to_f
|
33
|
+
number_with_precision(fval, precision: precision, significant: true)
|
34
|
+
end
|
35
|
+
|
36
|
+
def precision
|
37
|
+
sig = [
|
38
|
+
Precision.significant_digits(gt_node.content),
|
39
|
+
Precision.significant_digits(lte_node.content)
|
40
|
+
].max
|
41
|
+
sig - 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.significant_digits(number_s)
|
45
|
+
sig = 0
|
46
|
+
zeros = 0
|
47
|
+
number_s.each_char do |c|
|
48
|
+
next if (sig.zero? && c == '0') || c == '.'
|
49
|
+
if c == '0'
|
50
|
+
zeros += 1
|
51
|
+
else
|
52
|
+
sig += zeros + 1
|
53
|
+
zeros = 0
|
54
|
+
end
|
55
|
+
end
|
56
|
+
# this is not exactly significant digits
|
57
|
+
# trailing zeros are ignored
|
58
|
+
# because gt_node and lte_ndoe don't expect trailing zeros
|
59
|
+
sig
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Qti
|
2
|
+
module V1
|
3
|
+
module Models
|
4
|
+
module Numerics
|
5
|
+
class ScoringBase
|
6
|
+
def initialize(scoring_node)
|
7
|
+
@scoring_node = scoring_node
|
8
|
+
end
|
9
|
+
|
10
|
+
def equal_node
|
11
|
+
@scoring_node.equal_node
|
12
|
+
end
|
13
|
+
|
14
|
+
def gte_node
|
15
|
+
@scoring_node.gte_node
|
16
|
+
end
|
17
|
+
|
18
|
+
def lte_node
|
19
|
+
@scoring_node.lte_node
|
20
|
+
end
|
21
|
+
|
22
|
+
def gt_node
|
23
|
+
@scoring_node.gt_node
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Qti
|
2
|
+
module V1
|
3
|
+
module Models
|
4
|
+
module Numerics
|
5
|
+
class ScoringData
|
6
|
+
class UnsupportedNumreicType < StandardError; end
|
7
|
+
def initialize(node)
|
8
|
+
@scoring_node = ScoringNode.new(node)
|
9
|
+
end
|
10
|
+
|
11
|
+
def scoring_data
|
12
|
+
ExactMatch.new(@scoring_node).scoring_data ||
|
13
|
+
MarginError.new(@scoring_node).scoring_data ||
|
14
|
+
Precision.new(@scoring_node).scoring_data ||
|
15
|
+
WithinRange.new(@scoring_node).scoring_data ||
|
16
|
+
unknown_type
|
17
|
+
end
|
18
|
+
|
19
|
+
def unknown_type
|
20
|
+
raise UnsupportedNumreicType, 'Unsupported Numeric Type'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Qti
|
2
|
+
module V1
|
3
|
+
module Models
|
4
|
+
module Numerics
|
5
|
+
class ScoringNode
|
6
|
+
def initialize(node)
|
7
|
+
@node = node
|
8
|
+
end
|
9
|
+
|
10
|
+
def equal_node
|
11
|
+
@_equal_node ||= @node.at_xpath('.//xmlns:varequal')
|
12
|
+
end
|
13
|
+
|
14
|
+
def gte_node
|
15
|
+
@_gte_node ||= @node.at_xpath('.//xmlns:vargte')
|
16
|
+
end
|
17
|
+
|
18
|
+
def lte_node
|
19
|
+
@_lte_node ||= @node.at_xpath('.//xmlns:varlte')
|
20
|
+
end
|
21
|
+
|
22
|
+
def gt_node
|
23
|
+
@_gt_node ||= @node.at_xpath('.//xmlns:vargt')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'qti/v1/models/numerics/scoring_base'
|
2
|
+
|
3
|
+
module Qti
|
4
|
+
module V1
|
5
|
+
module Models
|
6
|
+
module Numerics
|
7
|
+
class WithinRange < ScoringBase
|
8
|
+
def initialize(scoring_node)
|
9
|
+
super(scoring_node)
|
10
|
+
end
|
11
|
+
|
12
|
+
def scoring_data
|
13
|
+
return unless valid?
|
14
|
+
Struct.new(
|
15
|
+
:id,
|
16
|
+
:type,
|
17
|
+
:start,
|
18
|
+
:end
|
19
|
+
).new(
|
20
|
+
gte_node.attributes['respident']&.value,
|
21
|
+
'withinARange',
|
22
|
+
gte_node.content,
|
23
|
+
lte_node.content
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def valid?
|
30
|
+
(gte_node && lte_node).present? && equal_node.nil?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/qti/version.rb
CHANGED
File without changes
|
@@ -0,0 +1,58 @@
|
|
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="i3c06f375b6b24a39925dc6a213421021" title="Quiz1.Margin.Error">
|
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="i028a5c60d8ce9dc63df3afefa2e9d01e" title="Question">
|
12
|
+
<itemmetadata>
|
13
|
+
<qtimetadata>
|
14
|
+
<qtimetadatafield>
|
15
|
+
<fieldlabel>question_type</fieldlabel>
|
16
|
+
<fieldentry>numerical_question</fieldentry>
|
17
|
+
</qtimetadatafield>
|
18
|
+
<qtimetadatafield>
|
19
|
+
<fieldlabel>points_possible</fieldlabel>
|
20
|
+
<fieldentry>1.0</fieldentry>
|
21
|
+
</qtimetadatafield>
|
22
|
+
<qtimetadatafield>
|
23
|
+
<fieldlabel>assessment_question_identifierref</fieldlabel>
|
24
|
+
<fieldentry>i874908082e9dce80647f2771296ca69c</fieldentry>
|
25
|
+
</qtimetadatafield>
|
26
|
+
</qtimetadata>
|
27
|
+
</itemmetadata>
|
28
|
+
<presentation>
|
29
|
+
<material>
|
30
|
+
<mattext texttype="text/html"><div><p>QQ1.margin error</p></div></mattext>
|
31
|
+
</material>
|
32
|
+
<response_str ident="response1" rcardinality="Single">
|
33
|
+
<render_fib fibtype="Decimal">
|
34
|
+
<response_label ident="answer1"/>
|
35
|
+
</render_fib>
|
36
|
+
</response_str>
|
37
|
+
</presentation>
|
38
|
+
<resprocessing>
|
39
|
+
<outcomes>
|
40
|
+
<decvar maxvalue="100" minvalue="0" varname="SCORE" vartype="Decimal"/>
|
41
|
+
</outcomes>
|
42
|
+
<respcondition continue="No">
|
43
|
+
<conditionvar>
|
44
|
+
<or>
|
45
|
+
<varequal respident="response1">77.0</varequal>
|
46
|
+
<and>
|
47
|
+
<vargte respident="response1">70.0</vargte>
|
48
|
+
<varlte respident="response1">84.0</varlte>
|
49
|
+
</and>
|
50
|
+
</or>
|
51
|
+
</conditionvar>
|
52
|
+
<setvar action="Set" varname="SCORE">100</setvar>
|
53
|
+
</respcondition>
|
54
|
+
</resprocessing>
|
55
|
+
</item>
|
56
|
+
</section>
|
57
|
+
</assessment>
|
58
|
+
</questestinterop>
|
@@ -0,0 +1,58 @@
|
|
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="Quiz1.precision">
|
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="i3ac3c6ccf4dd3dce4d5b5a5721edd5d5" title="Question">
|
12
|
+
<itemmetadata>
|
13
|
+
<qtimetadata>
|
14
|
+
<qtimetadatafield>
|
15
|
+
<fieldlabel>question_type</fieldlabel>
|
16
|
+
<fieldentry>numerical_question</fieldentry>
|
17
|
+
</qtimetadatafield>
|
18
|
+
<qtimetadatafield>
|
19
|
+
<fieldlabel>points_possible</fieldlabel>
|
20
|
+
<fieldentry>1.0</fieldentry>
|
21
|
+
</qtimetadatafield>
|
22
|
+
<qtimetadatafield>
|
23
|
+
<fieldlabel>assessment_question_identifierref</fieldlabel>
|
24
|
+
<fieldentry>i792f29a40533189006a3bd7ffee3c857</fieldentry>
|
25
|
+
</qtimetadatafield>
|
26
|
+
</qtimetadata>
|
27
|
+
</itemmetadata>
|
28
|
+
<presentation>
|
29
|
+
<material>
|
30
|
+
<mattext texttype="text/html"><div><p>QQ1.presision ss</p></div></mattext>
|
31
|
+
</material>
|
32
|
+
<response_str ident="response1" rcardinality="Single">
|
33
|
+
<render_fib fibtype="Decimal">
|
34
|
+
<response_label ident="answer1"/>
|
35
|
+
</render_fib>
|
36
|
+
</response_str>
|
37
|
+
</presentation>
|
38
|
+
<resprocessing>
|
39
|
+
<outcomes>
|
40
|
+
<decvar maxvalue="100" minvalue="0" varname="SCORE" vartype="Decimal"/>
|
41
|
+
</outcomes>
|
42
|
+
<respcondition continue="No">
|
43
|
+
<conditionvar>
|
44
|
+
<or>
|
45
|
+
<varequal respident="response1">1000.0</varequal>
|
46
|
+
<and>
|
47
|
+
<vargt respident="response1">999.995</vargt>
|
48
|
+
<varlte respident="response1">1000.005</varlte>
|
49
|
+
</and>
|
50
|
+
</or>
|
51
|
+
</conditionvar>
|
52
|
+
<setvar action="Set" varname="SCORE">100</setvar>
|
53
|
+
</respcondition>
|
54
|
+
</resprocessing>
|
55
|
+
</item>
|
56
|
+
</section>
|
57
|
+
</assessment>
|
58
|
+
</questestinterop>
|