qti 0.9.8 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
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>