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.
- checksums.yaml +4 -4
- data/README.markdown +0 -1
- data/lib/quby.rb +2 -10
- data/lib/quby/answers/entities/answer.rb +4 -2
- data/lib/quby/answers/services/outcome_calculation.rb +1 -2
- data/lib/quby/answers/services/score_calculator.rb +1 -3
- data/lib/quby/engine.rb +0 -1
- data/lib/quby/questionnaires.rb +1 -0
- data/lib/quby/questionnaires/api.rb +5 -1
- data/lib/quby/questionnaires/deserializer.rb +439 -0
- data/lib/quby/questionnaires/dsl.rb +12 -15
- data/lib/quby/questionnaires/entities.rb +1 -0
- data/lib/quby/questionnaires/entities/charting/line_chart.rb +23 -0
- data/lib/quby/questionnaires/entities/charting/overview_chart.rb +3 -1
- data/lib/quby/questionnaires/entities/definition.rb +3 -5
- data/lib/quby/questionnaires/entities/fields.rb +0 -15
- data/lib/quby/questionnaires/entities/question.rb +9 -32
- data/lib/quby/questionnaires/entities/questionnaire.rb +4 -15
- data/lib/quby/questionnaires/entities/questions/checkbox_question.rb +0 -24
- data/lib/quby/questionnaires/entities/questions/date_question.rb +0 -8
- data/lib/quby/questionnaires/entities/score_calculation.rb +36 -3
- data/lib/quby/questionnaires/repos.rb +1 -0
- data/lib/quby/questionnaires/repos/bundle_disk_repo.rb +51 -0
- data/lib/quby/table_backend/range_tree.rb +0 -51
- data/lib/quby/version.rb +1 -1
- data/spec/features/tables_spec.rb +40 -0
- data/spec/internal/log/test-events.log +531 -0
- data/spec/internal/log/test.log +21098 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-22-54.041.html +207 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-22-54.041.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-23.175.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-23.175.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-32.352.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-32.352.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-35.247.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-35.247.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-38.152.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-38.152.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-41.012.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-41.012.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-43.918.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-43.918.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-46.782.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-46.782.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-49.678.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-49.678.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-52.495.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-52.495.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-14-25-21.063.html +207 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-14-25-21.063.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-21-57.510.html +1 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-21-57.510.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-23-56.006.html +1 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-23-56.006.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-24-43.842.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-24-43.842.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-25-04.631.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-25-04.631.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-25-11.690.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-25-11.690.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-26-25.111.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-26-25.111.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-26-57.026.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-26-57.026.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-27-13.545.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-27-13.545.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-27-45.475.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-27-45.475.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-32-13.907.html +1 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-32-13.907.png +0 -0
- data/spec/quby/answers/services/answer_validations_spec.rb +8 -8
- data/spec/quby/answers/services/score_calculator_spec.rb +4 -14
- data/spec/quby/questionnaires/deserializer/questionnaire_spec.rb +237 -0
- data/spec/quby/questionnaires/dsl_spec.rb +0 -9
- data/spec/quby/questionnaires/entities/fields_spec.rb +3 -3
- data/spec/quby/questionnaires/entities/question_spec.rb +0 -8
- data/spec/quby/questionnaires/entities/questionnaire_spec.rb +2 -26
- data/spec/quby/table_backend/range_tree_spec.rb +46 -13
- data/spec/spec_helper.rb +1 -1
- metadata +108 -55
- data/lib/quby/lookup_table.rb +0 -29
- data/lib/quby/lookup_table_repo.rb +0 -24
- data/lib/quby/questionnaires/dsl/base.rb +0 -20
- data/lib/quby/questionnaires/dsl/calls_custom_methods.rb +0 -29
- data/lib/quby/questionnaires/dsl/charting/bar_chart_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/charting/chart_builder.rb +0 -91
- data/lib/quby/questionnaires/dsl/charting/line_chart_builder.rb +0 -47
- data/lib/quby/questionnaires/dsl/charting/overview_chart_builder.rb +0 -31
- data/lib/quby/questionnaires/dsl/charting/radar_chart_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/helpers.rb +0 -51
- data/lib/quby/questionnaires/dsl/panel_builder.rb +0 -80
- data/lib/quby/questionnaires/dsl/question_builder.rb +0 -40
- data/lib/quby/questionnaires/dsl/questionnaire_builder.rb +0 -252
- data/lib/quby/questionnaires/dsl/questions/base.rb +0 -179
- data/lib/quby/questionnaires/dsl/questions/checkbox_question_builder.rb +0 -20
- data/lib/quby/questionnaires/dsl/questions/date_question_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/questions/deprecated_question_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/questions/float_question_builder.rb +0 -21
- data/lib/quby/questionnaires/dsl/questions/integer_question_builder.rb +0 -21
- data/lib/quby/questionnaires/dsl/questions/radio_question_builder.rb +0 -20
- data/lib/quby/questionnaires/dsl/questions/select_question_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/questions/string_question_builder.rb +0 -20
- data/lib/quby/questionnaires/dsl/questions/text_question_builder.rb +0 -22
- data/lib/quby/questionnaires/dsl/score_builder.rb +0 -22
- data/lib/quby/questionnaires/dsl/standardized_panel_generators.rb +0 -33
- data/lib/quby/questionnaires/dsl/table_builder.rb +0 -48
- data/lib/quby/questionnaires/services/definition_validator.rb +0 -298
- data/spec/benchmarks/load_normscore_csv.rb +0 -18
- data/spec/quby/lookup_table_repo_spec.rb +0 -20
- data/spec/quby/lookup_table_spec.rb +0 -38
- data/spec/quby/questionnaires/dsl/calls_custom_methods_spec.rb +0 -38
- data/spec/quby/questionnaires/dsl/charting/bar_chart_builder_spec.rb +0 -41
- data/spec/quby/questionnaires/dsl/charting/chart_builder_spec.rb +0 -127
- data/spec/quby/questionnaires/dsl/charting/line_chart_builder_spec.rb +0 -58
- data/spec/quby/questionnaires/dsl/charting/radar_chart_builder_spec.rb +0 -41
- data/spec/quby/questionnaires/dsl/helpers_spec.rb +0 -80
- data/spec/quby/questionnaires/dsl/questionnaire_builder_spec.rb +0 -480
- data/spec/quby/questionnaires/services/definition_validator_spec.rb +0 -793
- 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
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
@@ -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 =
|
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
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
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
|
-
@
|
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
|