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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/qti.rb +7 -0
  3. data/lib/qti/v1/models/interactions.rb +0 -1
  4. data/lib/qti/v1/models/interactions/numeric_interaction.rb +4 -14
  5. data/lib/qti/v1/models/numerics/exact_match.rb +43 -0
  6. data/lib/qti/v1/models/numerics/margin_error.rb +51 -0
  7. data/lib/qti/v1/models/numerics/precision.rb +65 -0
  8. data/lib/qti/v1/models/numerics/scoring_base.rb +29 -0
  9. data/lib/qti/v1/models/numerics/scoring_data.rb +26 -0
  10. data/lib/qti/v1/models/numerics/scoring_node.rb +29 -0
  11. data/lib/qti/v1/models/numerics/within_range.rb +36 -0
  12. data/lib/qti/version.rb +1 -1
  13. data/spec/fixtures/items_1.2/{numeric.xml → numeric_exact_match.xml} +0 -0
  14. data/spec/fixtures/items_1.2/numeric_margin_error.xml +58 -0
  15. data/spec/fixtures/items_1.2/numeric_precision.xml +58 -0
  16. data/spec/fixtures/items_1.2/numeric_within_range.xml +53 -0
  17. data/spec/gemfiles/nokogiri-1.8.gemfile.lock +35 -18
  18. data/spec/lib/qti/v1/models/interactions/numeric_interaction_spec.rb +92 -27
  19. data/spec/lib/qti/v1/models/numerics/exact_match_spec.rb +84 -0
  20. data/spec/lib/qti/v1/models/numerics/margin_error_spec.rb +80 -0
  21. data/spec/lib/qti/v1/models/numerics/precision_spec.rb +88 -0
  22. data/spec/lib/qti/v1/models/numerics/scoring_base_spec.rb +35 -0
  23. data/spec/lib/qti/v1/models/numerics/scoring_node_spec.rb +29 -0
  24. data/spec/lib/qti/v1/models/numerics/within_range_spec.rb +64 -0
  25. data/spec/spec_helper.rb +1 -0
  26. metadata +44 -17
  27. data/spec/gemfiles/nokogiri-1.6.gemfile.lock +0 -127
  28. data/spec/gemfiles/rails-4.2.gemfile.lock +0 -204
  29. data/spec/gemfiles/rails-5.0.gemfile.lock +0 -210
  30. data/spec/gemfiles/rails-5.1.gemfile.lock +0 -210
  31. data/spec/gemfiles/sanitize-4.2.gemfile.lock +0 -127
  32. 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: b0b2571404f88af47659de553891bc760d1ee13f
4
- data.tar.gz: 8e68acc2256a603807a42b04ad34277e92f92225
3
+ metadata.gz: d8fb6165196a5601ea5d6bb415d14897246d6ef7
4
+ data.tar.gz: a30a49e90c67b24c0762a01c76688520f1633f7b
5
5
  SHA512:
6
- metadata.gz: 9f939252ce6df39c30b75757648bb5350397a8e8e1f81584f906b15ab00578e48f8ddfea4bbba47e53c5a8862a796137ee65dd17a29ec64f3f942d0b50789821
7
- data.tar.gz: 6c068d7a32c9d741e3f2f9f7a5f18541b6d4ece8f774409989aca382e57d4aa3c68dd57c0522698bb0fe7086e6ede024b8db3bd7aa2c67173b0474f5e4687dfc
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.content,
36
- nil,
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:varequal')
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
@@ -1,3 +1,3 @@
1
1
  module Qti
2
- VERSION = '0.9.8'.freeze
2
+ VERSION = '0.9.9'.freeze
3
3
  end
@@ -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">&lt;div&gt;&lt;p&gt;QQ1.margin error&lt;/p&gt;&lt;/div&gt;</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">&lt;div&gt;&lt;p&gt;QQ1.presision ss&lt;/p&gt;&lt;/div&gt;</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>