quby 4.0.2 → 5.0.0.pre4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/README.markdown +0 -1
  3. data/lib/quby.rb +2 -10
  4. data/lib/quby/answers/entities/answer.rb +4 -2
  5. data/lib/quby/answers/services/outcome_calculation.rb +1 -2
  6. data/lib/quby/answers/services/score_calculator.rb +1 -3
  7. data/lib/quby/engine.rb +0 -1
  8. data/lib/quby/questionnaires.rb +1 -0
  9. data/lib/quby/questionnaires/api.rb +5 -1
  10. data/lib/quby/questionnaires/deserializer.rb +439 -0
  11. data/lib/quby/questionnaires/dsl.rb +12 -15
  12. data/lib/quby/questionnaires/entities.rb +1 -0
  13. data/lib/quby/questionnaires/entities/charting/line_chart.rb +23 -0
  14. data/lib/quby/questionnaires/entities/charting/overview_chart.rb +3 -1
  15. data/lib/quby/questionnaires/entities/definition.rb +3 -5
  16. data/lib/quby/questionnaires/entities/fields.rb +0 -15
  17. data/lib/quby/questionnaires/entities/question.rb +9 -32
  18. data/lib/quby/questionnaires/entities/questionnaire.rb +4 -15
  19. data/lib/quby/questionnaires/entities/questions/checkbox_question.rb +0 -24
  20. data/lib/quby/questionnaires/entities/questions/date_question.rb +0 -8
  21. data/lib/quby/questionnaires/entities/score_calculation.rb +36 -3
  22. data/lib/quby/questionnaires/repos.rb +1 -0
  23. data/lib/quby/questionnaires/repos/bundle_disk_repo.rb +51 -0
  24. data/lib/quby/table_backend/range_tree.rb +0 -51
  25. data/lib/quby/version.rb +1 -1
  26. data/spec/features/tables_spec.rb +40 -0
  27. data/spec/internal/log/test-events.log +531 -0
  28. data/spec/internal/log/test.log +21098 -0
  29. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-22-54.041.html +207 -0
  30. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-22-54.041.png +0 -0
  31. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-23.175.html +323 -0
  32. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-23.175.png +0 -0
  33. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-32.352.html +323 -0
  34. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-32.352.png +0 -0
  35. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-35.247.html +323 -0
  36. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-35.247.png +0 -0
  37. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-38.152.html +323 -0
  38. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-38.152.png +0 -0
  39. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-41.012.html +323 -0
  40. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-41.012.png +0 -0
  41. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-43.918.html +323 -0
  42. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-43.918.png +0 -0
  43. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-46.782.html +323 -0
  44. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-46.782.png +0 -0
  45. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-49.678.html +323 -0
  46. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-49.678.png +0 -0
  47. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-52.495.html +323 -0
  48. data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-52.495.png +0 -0
  49. data/spec/internal/tmp/capybara/screenshot_2020-10-27-14-25-21.063.html +207 -0
  50. data/spec/internal/tmp/capybara/screenshot_2020-10-27-14-25-21.063.png +0 -0
  51. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-21-57.510.html +1 -0
  52. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-21-57.510.png +0 -0
  53. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-23-56.006.html +1 -0
  54. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-23-56.006.png +0 -0
  55. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-24-43.842.html +12 -0
  56. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-24-43.842.png +0 -0
  57. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-25-04.631.html +12 -0
  58. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-25-04.631.png +0 -0
  59. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-25-11.690.html +12 -0
  60. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-25-11.690.png +0 -0
  61. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-26-25.111.html +12 -0
  62. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-26-25.111.png +0 -0
  63. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-26-57.026.html +12 -0
  64. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-26-57.026.png +0 -0
  65. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-27-13.545.html +12 -0
  66. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-27-13.545.png +0 -0
  67. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-27-45.475.html +12 -0
  68. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-27-45.475.png +0 -0
  69. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-32-13.907.html +1 -0
  70. data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-32-13.907.png +0 -0
  71. data/spec/quby/answers/services/answer_validations_spec.rb +8 -8
  72. data/spec/quby/answers/services/score_calculator_spec.rb +4 -14
  73. data/spec/quby/questionnaires/deserializer/questionnaire_spec.rb +237 -0
  74. data/spec/quby/questionnaires/dsl_spec.rb +0 -9
  75. data/spec/quby/questionnaires/entities/fields_spec.rb +3 -3
  76. data/spec/quby/questionnaires/entities/question_spec.rb +0 -8
  77. data/spec/quby/questionnaires/entities/questionnaire_spec.rb +2 -26
  78. data/spec/quby/table_backend/range_tree_spec.rb +46 -13
  79. data/spec/spec_helper.rb +1 -1
  80. metadata +108 -55
  81. data/lib/quby/lookup_table.rb +0 -29
  82. data/lib/quby/lookup_table_repo.rb +0 -24
  83. data/lib/quby/questionnaires/dsl/base.rb +0 -20
  84. data/lib/quby/questionnaires/dsl/calls_custom_methods.rb +0 -29
  85. data/lib/quby/questionnaires/dsl/charting/bar_chart_builder.rb +0 -18
  86. data/lib/quby/questionnaires/dsl/charting/chart_builder.rb +0 -91
  87. data/lib/quby/questionnaires/dsl/charting/line_chart_builder.rb +0 -47
  88. data/lib/quby/questionnaires/dsl/charting/overview_chart_builder.rb +0 -31
  89. data/lib/quby/questionnaires/dsl/charting/radar_chart_builder.rb +0 -18
  90. data/lib/quby/questionnaires/dsl/helpers.rb +0 -51
  91. data/lib/quby/questionnaires/dsl/panel_builder.rb +0 -80
  92. data/lib/quby/questionnaires/dsl/question_builder.rb +0 -40
  93. data/lib/quby/questionnaires/dsl/questionnaire_builder.rb +0 -252
  94. data/lib/quby/questionnaires/dsl/questions/base.rb +0 -179
  95. data/lib/quby/questionnaires/dsl/questions/checkbox_question_builder.rb +0 -20
  96. data/lib/quby/questionnaires/dsl/questions/date_question_builder.rb +0 -18
  97. data/lib/quby/questionnaires/dsl/questions/deprecated_question_builder.rb +0 -18
  98. data/lib/quby/questionnaires/dsl/questions/float_question_builder.rb +0 -21
  99. data/lib/quby/questionnaires/dsl/questions/integer_question_builder.rb +0 -21
  100. data/lib/quby/questionnaires/dsl/questions/radio_question_builder.rb +0 -20
  101. data/lib/quby/questionnaires/dsl/questions/select_question_builder.rb +0 -18
  102. data/lib/quby/questionnaires/dsl/questions/string_question_builder.rb +0 -20
  103. data/lib/quby/questionnaires/dsl/questions/text_question_builder.rb +0 -22
  104. data/lib/quby/questionnaires/dsl/score_builder.rb +0 -22
  105. data/lib/quby/questionnaires/dsl/standardized_panel_generators.rb +0 -33
  106. data/lib/quby/questionnaires/dsl/table_builder.rb +0 -48
  107. data/lib/quby/questionnaires/services/definition_validator.rb +0 -298
  108. data/spec/benchmarks/load_normscore_csv.rb +0 -18
  109. data/spec/quby/lookup_table_repo_spec.rb +0 -20
  110. data/spec/quby/lookup_table_spec.rb +0 -38
  111. data/spec/quby/questionnaires/dsl/calls_custom_methods_spec.rb +0 -38
  112. data/spec/quby/questionnaires/dsl/charting/bar_chart_builder_spec.rb +0 -41
  113. data/spec/quby/questionnaires/dsl/charting/chart_builder_spec.rb +0 -127
  114. data/spec/quby/questionnaires/dsl/charting/line_chart_builder_spec.rb +0 -58
  115. data/spec/quby/questionnaires/dsl/charting/radar_chart_builder_spec.rb +0 -41
  116. data/spec/quby/questionnaires/dsl/helpers_spec.rb +0 -80
  117. data/spec/quby/questionnaires/dsl/questionnaire_builder_spec.rb +0 -480
  118. data/spec/quby/questionnaires/services/definition_validator_spec.rb +0 -793
  119. data/spec/support/examples_for_chart_builders.rb +0 -59
@@ -1,27 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'quby/questionnaires/dsl/base'
4
- require 'quby/questionnaires/dsl/helpers'
5
- require 'quby/questionnaires/dsl/questionnaire_builder'
6
-
7
3
  module Quby
8
4
  module Questionnaires
9
5
  module DSL
6
+ # Deprecated, precompile elsewhere and use from_json
10
7
  def self.build_from_definition(definition)
11
- Entities::Questionnaire.new(definition.key, last_update: definition.timestamp).tap do |questionnaire|
12
- builder = QuestionnaireBuilder.new(questionnaire)
13
- builder.instance_eval(definition.sourcecode, definition.path) if definition.sourcecode
14
- questionnaire.callback_after_dsl_enhance_on_questions
15
- end
8
+ compiled = Quby::Compiler.compile(definition.key, definition.sourcecode, seeds: [], lookup_tables: {}, path: definition.path)
9
+ data = JSON.parse(compiled[:outputs][:quby_frontend_v1].content)
10
+ Deserializer.from_json(data)
16
11
  end
17
12
 
13
+ # Deprecated, precompile elsewhere and use from_json
18
14
  def self.build(key, sourcecode = nil, timestamp: nil, &block)
19
- Entities::Questionnaire.new(key, last_update: timestamp).tap do |questionnaire|
20
- builder = QuestionnaireBuilder.new(questionnaire)
21
- builder.instance_eval(sourcecode, key) if sourcecode
22
- builder.instance_eval(&block) if block
23
- questionnaire.callback_after_dsl_enhance_on_questions
24
- end
15
+ compiled = Quby::Compiler.compile(key, sourcecode, seeds: [], lookup_tables: {}, &block)
16
+ data = JSON.parse(compiled[:outputs][:quby_frontend_v1].content)
17
+ Deserializer.from_json(data)
18
+ end
19
+
20
+ def self.from_json(hash)
21
+ Deserializer.from_json(hash)
25
22
  end
26
23
  end
27
24
  end
@@ -13,6 +13,7 @@ require 'quby/questionnaires/entities/fields'
13
13
 
14
14
  require 'quby/questionnaires/entities/charting/charts'
15
15
  require 'quby/questionnaires/entities/charting/chart'
16
+ require 'quby/questionnaires/entities/charting/overview_chart'
16
17
  require 'quby/questionnaires/entities/charting/line_chart'
17
18
  require 'quby/questionnaires/entities/charting/bar_chart'
18
19
  require 'quby/questionnaires/entities/charting/radar_chart'
@@ -27,10 +27,33 @@ module Quby
27
27
  self.clinically_relevant_change = clinically_relevant_change
28
28
  end
29
29
 
30
+ def baseline
31
+ @baseline_proc ||= make_baseline_proc
32
+ end
33
+
30
34
  def tonality=(value)
31
35
  fail "Invalid tonality: #{value}" unless [:higher_is_better, :lower_is_better].include?(value)
32
36
  @tonality = value
33
37
  end
38
+
39
+ private
40
+
41
+ def make_baseline_proc
42
+ return unless @baseline
43
+
44
+ case @baseline
45
+ when Hash
46
+ ->(age, gender) {
47
+ age_match = @baseline.find { |age_range, _v| age && age_range === age }
48
+ hash_by_gender = (age_match&.last || @baseline.stringify_keys["default"])
49
+
50
+ gender_match = hash_by_gender.find {|gender_key, _v| gender && gender_key.to_s == gender.to_s }
51
+ gender_match&.last || hash_by_gender.stringify_keys['default']
52
+ }
53
+ else
54
+ ->(age, gender) { @baseline }
55
+ end
56
+ end
34
57
  end
35
58
  end
36
59
  end
@@ -11,7 +11,9 @@ module Quby
11
11
  # @return Integer
12
12
  attr_accessor :y_max
13
13
 
14
- def initialize
14
+ def initialize(subscore: nil, y_max: nil)
15
+ self.subscore = subscore
16
+ self.y_max = y_max
15
17
  end
16
18
  end
17
19
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_model'
4
- require 'quby/questionnaires/services/definition_validator'
5
4
 
6
5
  module Quby
7
6
  module Questionnaires
@@ -10,16 +9,15 @@ module Quby
10
9
  extend ActiveModel::Naming
11
10
  include ActiveModel::Validations
12
11
 
13
- attr_accessor :key, :sourcecode, :timestamp, :path
12
+ attr_accessor :key, :sourcecode, :json, :timestamp, :path
14
13
 
15
- def initialize(key:, path:, sourcecode: "", timestamp: nil)
14
+ def initialize(key:, path:, sourcecode: "", json: nil, timestamp: nil)
16
15
  @path = path
17
16
  @key = key
18
17
  @sourcecode = sourcecode
18
+ @json = json
19
19
  @timestamp = timestamp
20
20
  end
21
-
22
- validates_with Services::DefinitionValidator
23
21
  end
24
22
  end
25
23
  end
@@ -35,11 +35,6 @@ module Quby
35
35
  new_answer_keys = Set.new(question.answer_keys)
36
36
  new_input_keys = Set.new(question.input_keys)
37
37
 
38
- # This is probably the best place to ensure that keys don't collide. However,our current set of questionnaires
39
- # does have a few collisions between +v_1+ option +a9+ and its subquestion +v_1_a9+, so we have excluded
40
- # those questionnaires from this check through @questionnaire.check_key_clashes.
41
- check_key_clashes(new_answer_keys, new_input_keys) if @questionnaire.check_key_clashes
42
-
43
38
  @question_hash[question.key] = question
44
39
  @input_keys.merge(new_input_keys)
45
40
  @answer_keys.merge(new_answer_keys)
@@ -48,16 +43,6 @@ module Quby
48
43
  end
49
44
  end
50
45
 
51
- def check_key_clashes(new_answer_keys, new_input_keys)
52
- if @answer_keys.intersect?(new_answer_keys)
53
- fail "Duplicate answer keys: #{@answer_keys.intersection(new_answer_keys).inspect}"
54
- end
55
-
56
- if @input_keys.intersect?(new_input_keys)
57
- fail "Duplicate input keys: #{@input_keys.intersection(new_input_keys).inspect}"
58
- end
59
- end
60
-
61
46
  def key_in_use?(key)
62
47
  @question_hash.key?(key) || input_keys.include?(key.to_sym)
63
48
  end
@@ -8,8 +8,6 @@ module Quby
8
8
  class Question < Item
9
9
  MARKDOWN_ATTRIBUTES = %w(description title).freeze
10
10
 
11
- set_callback :after_dsl_enhance, :expand_depends_on_input_keys
12
-
13
11
  # Standard attributes
14
12
  attr_accessor :key
15
13
  validates :key, presence: true, 'quby/type': {is_a: Symbol}
@@ -140,7 +138,7 @@ module Quby
140
138
  @description = options[:description]
141
139
  @display_modes = options[:display_modes]
142
140
  @presentation = options[:presentation]
143
- @validations = []
141
+ @validations = options[:validations] || []
144
142
  @parent = options[:parent]
145
143
  @hidden = options[:hidden]
146
144
  @table = options[:table]
@@ -150,12 +148,13 @@ module Quby
150
148
  @deselectable = (options[:deselectable].nil? || options[:deselectable])
151
149
  @disallow_bulk = options[:disallow_bulk]
152
150
  @score_header = options[:score_header] || :none
153
- @sets_textvar = "#{questionnaire.key}_#{options[:sets_textvar]}" if options[:sets_textvar]
151
+ @sets_textvar = options[:sets_textvar]
154
152
  @unit = options[:unit]
155
153
  @lines = options[:lines] || 6
156
154
  @cols = options[:cols] || 40
157
155
  @default_invisible = options[:default_invisible] || false
158
- @labels ||= []
156
+ @labels = options[:labels] || []
157
+ @size = options[:size]
159
158
 
160
159
  @col_span = options[:col_span] || 1
161
160
  @row_span = options[:row_span] || 1
@@ -169,16 +168,6 @@ module Quby
169
168
  @input_data = {}
170
169
  @input_data[:value_tooltip] = true if options[:value_tooltip]
171
170
 
172
- # Require subquestions of required questions by default
173
- options[:required] = true if @parent&.validations&.first&.fetch(:type, nil) == :requires_answer
174
- @validations << {type: :requires_answer, explanation: options[:error_explanation]} if options[:required]
175
-
176
- if @type == :float
177
- @validations << {type: :valid_float, explanation: options[:error_explanation]}
178
- elsif @type == :integer
179
- @validations << {type: :valid_integer, explanation: options[:error_explanation]}
180
- end
181
-
182
171
  if options[:minimum] and (@type == :integer || @type == :float)
183
172
  fail "deprecated" # pretty sure this is not used anywhere
184
173
  end
@@ -186,17 +175,6 @@ module Quby
186
175
  fail "deprecated" # pretty sure this is not used anywhere
187
176
  end
188
177
  @default_position = options[:default_position]
189
-
190
- if @question_group
191
- if @group_minimum_answered
192
- @validations << {type: :answer_group_minimum, group: @question_group, value: @group_minimum_answered,
193
- explanation: options[:error_explanation]}
194
- end
195
- if @group_maximum_answered
196
- @validations << {type: :answer_group_maximum, group: @question_group, value: @group_maximum_answered,
197
- explanation: options[:error_explanation]}
198
- end
199
- end
200
178
  end
201
179
  # rubocop:enable CyclomaticComplexity, Metrics/MethodLength
202
180
 
@@ -212,12 +190,11 @@ module Quby
212
190
  @context_free_title || @title
213
191
  end
214
192
 
215
- def expand_depends_on_input_keys
216
- return unless @depends_on
217
- @depends_on = questionnaire.expand_input_keys(@depends_on)
218
- @extra_data[:"depends-on"] = @depends_on.to_json
219
- rescue => e
220
- raise e.class, "Question #{key} depends_on contains an error: #{e.message}"
193
+ def extra_data
194
+ result = @extra_data
195
+ result = result.merge(:"depends-on" => @depends_on.to_json) if @depends_on
196
+ result = result.merge(:placeholder => @options.find { |option| option.placeholder }&.key)
197
+ result
221
198
  end
222
199
 
223
200
  def col_span
@@ -29,8 +29,9 @@ module Quby
29
29
 
30
30
  RESPONDENT_TYPES = %i( profess patient parent second_parent teacher caregiver )
31
31
 
32
- def initialize(key, last_update: Time.now)
32
+ def initialize(key, attributes = {}, last_update: Time.now)
33
33
  @key = key
34
+ @attributes = attributes
34
35
  @sbg_domains = []
35
36
  @last_update = Time.at(last_update.to_i)
36
37
  @score_calculations = {}.with_indifferent_access
@@ -54,6 +55,8 @@ module Quby
54
55
  @lookup_tables = {}
55
56
  end
56
57
 
58
+ attr_reader :attributes
59
+
57
60
  attr_accessor :key
58
61
  attr_accessor :title
59
62
  attr_accessor :description
@@ -136,20 +139,6 @@ module Quby
136
139
  end
137
140
  end
138
141
 
139
- def callback_after_dsl_enhance_on_questions
140
- question_hash.each_value do |q|
141
- q.run_callbacks :after_dsl_enhance
142
- end
143
- ensure_scores_have_schemas if Quby::Settings.require_score_schemas
144
- end
145
-
146
- def ensure_scores_have_schemas
147
- missing_schemas = scores.map(&:key).map(&:to_s) - score_schemas.keys
148
- missing_schemas.each do |key|
149
- errors.add "Score #{key}", 'is missing a score schema'
150
- end
151
- end
152
-
153
142
  def validate_questions
154
143
  question_hash.each_value do |q|
155
144
  unless q.valid?
@@ -24,30 +24,6 @@ module Quby
24
24
  @uncheck_all_option = options[:uncheck_all_option]
25
25
  @maximum_checked_allowed = options[:maximum_checked_allowed]
26
26
  @minimum_checked_required = options[:minimum_checked_required]
27
-
28
- if @check_all_option
29
- @validations << {type: :not_all_checked,
30
- check_all_key: @check_all_option,
31
- explanation: options[:error_explanation]}
32
- end
33
-
34
- if @uncheck_all_option
35
- @validations << {type: :too_many_checked,
36
- uncheck_all_key: @uncheck_all_option,
37
- explanation: options[:error_explanation]}
38
- end
39
-
40
- if @maximum_checked_allowed
41
- @validations << {type: :maximum_checked_allowed,
42
- maximum_checked_value: @maximum_checked_allowed,
43
- explanation: options[:error_explanation]}
44
- end
45
-
46
- if @minimum_checked_required
47
- @validations << {type: :minimum_checked_required,
48
- minimum_checked_value: @minimum_checked_required,
49
- explanation: options[:error_explanation]}
50
- end
51
27
  end
52
28
 
53
29
  def variable_descriptions
@@ -32,14 +32,6 @@ module Quby
32
32
  component_key = options[:"#{component}_key"] || "#{key}_#{COMPONENT_KEYS[component]}"
33
33
  instance_variable_set("@#{component}_key", component_key.to_sym)
34
34
  end
35
-
36
- add_date_validation(options[:error_explanation])
37
- end
38
-
39
- def add_date_validation(explanation)
40
- @validations << {type: :valid_date,
41
- subtype: :"valid_date_#{components.sort.join('_')}",
42
- explanation: explanation}
43
35
  end
44
36
 
45
37
  def claimed_keys
@@ -3,15 +3,48 @@
3
3
  module Quby
4
4
  module Questionnaires
5
5
  module Entities
6
+ # method_source gem gives us the full score source code including the initial `score() do` DSL call.
7
+ # This module helps strip off that outer DSL call.
8
+ module StripOuterScoreCall
9
+ def self.score(*args, &block)
10
+ block
11
+ end
12
+
13
+ def self.variable(*args, &block)
14
+ block
15
+ end
16
+
17
+ def self.attention(*args, &block)
18
+ block
19
+ end
20
+
21
+ def self.alarm(*args, &block)
22
+ block
23
+ end
24
+
25
+ def completion(*args, &block)
26
+ block
27
+ end
28
+ end
29
+
6
30
  class ScoreCalculation
7
- attr_accessor :key, :label, :sbg_key, :options, :calculation
31
+ attr_accessor :key, :label, :sbg_key, :options
8
32
 
9
33
  def initialize(key, options, &block)
10
34
  @key = key
11
35
  @label = options[:label]
12
36
  @sbg_key = options[:sbg_key]
13
- @options = options
14
- @calculation = block
37
+ @options = options[:options] || options # TODO remove `|| options`
38
+ @sourcecode = options[:sourcecode]
39
+ @block = block
40
+ end
41
+
42
+ def calculation
43
+ if @block
44
+ @block
45
+ else
46
+ StripOuterScoreCall.instance_eval(@sourcecode)
47
+ end
15
48
  end
16
49
 
17
50
  def score
@@ -6,6 +6,7 @@ module Quby
6
6
  autoload :Base, 'quby/questionnaires/repos/base'
7
7
  autoload :MemoryRepo, 'quby/questionnaires/repos/memory_repo'
8
8
  autoload :DiskRepo, 'quby/questionnaires/repos/disk_repo'
9
+ autoload :BundleDiskRepo, 'quby/questionnaires/repos/bundle_disk_repo'
9
10
 
10
11
  QuestionnaireNotFound = Class.new(StandardError)
11
12
  DuplicateQuestionnaire = Class.new(StandardError)
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'quby/questionnaires/repos'
4
+ require 'quby/questionnaires'
5
+
6
+ module Quby
7
+ module Questionnaires
8
+ module Repos
9
+ class BundleDiskRepo < Base
10
+ attr_reader :path
11
+
12
+ def initialize(path)
13
+ @path = path
14
+ @questionnaire_cache = {}
15
+ end
16
+
17
+ def keys
18
+ Dir[File.join(path, "*.rb")].map do |filename|
19
+ File.basename(filename, '.rb')
20
+ end
21
+ end
22
+
23
+ def find(key)
24
+ fail(QuestionnaireNotFound, key) unless exists?(key)
25
+ json = read(key)
26
+ timestamp = Time.zone.parse(read(key)["last_update"])
27
+ Entities::Definition.new(key: key, path: questionnaire_path(key), json: json, timestamp: timestamp)
28
+ end
29
+
30
+ def exists?(key)
31
+ questionnaire_path = questionnaire_path(key)
32
+ File.exist?(questionnaire_path)
33
+ end
34
+
35
+ def timestamp(key)
36
+ Time.zone.parse(read(key)["last_update"])
37
+ end
38
+
39
+ private
40
+
41
+ def read(key)
42
+ JSON.parse(File.read(questionnaire_path(key)))
43
+ end
44
+
45
+ def questionnaire_path(key)
46
+ File.join(path,key, "quby-frontend-v1.json")
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -2,8 +2,6 @@
2
2
 
3
3
  # A lookup tree to find values by multiple arguments.
4
4
  #
5
- # Created by LookupTable from a csv file or by add_lookup_tree from dsl.
6
- #
7
5
  # Example tree:
8
6
  # Inhibitie:
9
7
  # male:
@@ -36,27 +34,6 @@ module Quby::TableBackend
36
34
  @tree = tree
37
35
  end
38
36
 
39
- # load csv data into a tree.
40
- # each row is a path through the tree.
41
- # String and float types are used to make an exact match.
42
- # A range is always a range between two floats where the range is between
43
- # the low value (inclusive) and the high value (exclusive),
44
- # written as 4:5 (low:high). These boundaries can be given as floats or
45
- # integers, but internally they are always treated as a floats.
46
- # The low and high values of a range cannot be equal.
47
- # Use minfinity or infinity to create infinite ranges.
48
- #
49
- # @params levels [Array<String>] An array of column names
50
- # @param compare [Array<String>]An array of lookup types (string, float or range) for each column
51
- # @param data [Array<Array<>>] The rows describing a path through the tree.
52
- def self.from_csv(levels:, compare:, data:)
53
- tree = data.each_with_object({}) do |row, tree|
54
- add_to_tree(tree, row, levels, compare)
55
- end
56
- new(levels: levels, tree: tree)
57
- end
58
-
59
-
60
37
  # Given a parameters hash that contains a value or range for every
61
38
  # level in the tree, find and return the normscore.
62
39
  # ie. `lookup({age: 10, raw: 5, scale: 'Inhibitie', gender: 'male'})` => 39
@@ -67,34 +44,6 @@ module Quby::TableBackend
67
44
 
68
45
  private
69
46
 
70
- def self.add_to_tree(tree, (value, *path), (level, *levels), (compare, *compares))
71
- key = case compare
72
- when 'string' then value.to_s
73
- when 'float' then parse_float(value)
74
- when 'range' then create_range(value)
75
- end
76
-
77
- if levels.empty?
78
- return key
79
- end
80
-
81
- tree.merge! key => add_to_tree(tree[key] || {}, path, levels, compares)
82
- end
83
-
84
- def self.create_range(value)
85
- min, max = value.split(':').map { |val| parse_float(val) }
86
- fail 'Cannot create range between two equal values' if min == max
87
- (min...max)
88
- end
89
-
90
- def self.parse_float(value)
91
- case value
92
- when 'infinity' then Float::INFINITY
93
- when 'minfinity' then -Float::INFINITY
94
- else Float(value)
95
- end
96
- end
97
-
98
47
  # All parameters must be present to do a lookup but the order does not matter.
99
48
  def validate_parameters(parameters)
100
49
  if @levels[0...-1].sort != parameters.keys.map(&:to_s).sort