class Quby::Answers::Services::ScoreCalculator

Public Class Methods

calculate(**kwargs, &block) click to toggle source

Evaluates block within the context of a new calculator instance. All instance methods are accessible.

# File lib/quby/answers/services/score_calculator.rb, line 25
def self.calculate(**kwargs, &block)
  instance = new(**kwargs)
  result = instance.instance_eval(&block)
  result = result.merge(referenced_values: instance.referenced_values) if result.respond_to?(:merge)
  result
end
new(questionnaire:, values:, timestamp:, patient_attrs: {}, respondent_attrs: {}) click to toggle source

Public: Initialize a new ScoreCalculator

values - The Hash values describes the keys of questions and the values

of the answer given to that question.

timestamp - The Time to be used to calculate the age of the patient. patient_attrs - A Hash describing extra patient information (default: {})

:birthyear - The Integer birthyear of the patient to be used in
             score calculation (optional)
:gender - The Symbol gender of the patient, must be one of:
          :male, :female or :unknown (optional)

respondent_attrs - A Hash describing respondent information (default: {})

:respondent_type - The Symbol or String type of respondent
# File lib/quby/answers/services/score_calculator.rb, line 44
def initialize(questionnaire:, values:, timestamp:, patient_attrs: {}, respondent_attrs: {})
  @questionnaire = questionnaire
  @values = values
  @timestamp = timestamp
  @patient = Entities::Patient.new(patient_attrs)
  @respondent = Entities::Respondent.new(respondent_attrs)
  @score = {}
  @referenced_values = []
end

Public Instance Methods

age() click to toggle source

Public: Returns the Integer age of the patient, or nil if it's not known.

# File lib/quby/answers/services/score_calculator.rb, line 199
def age
  @patient.age_at @timestamp
end
ensure_answer_values_for(*keys, minimum_present: keys.flatten(1).size, missing_values: []) click to toggle source

Public: Ensure given question_keys have answers. Strings with nothing but whitespace are not considered answered.

*keys - A list of keys to check if an answer is given *minimum_present - defaults to all *missing_values - extra values to consider missing.

# File lib/quby/answers/services/score_calculator.rb, line 244
def ensure_answer_values_for(*keys, minimum_present: keys.flatten(1).size, missing_values: [])
  keys = keys.flatten(1).map(&:to_s)
  # we also consider '' and whitespace to be not filled in, as well as nil values or missing keys
  unanswered_keys = keys.select { |key| missing_value?(@values[key], missing_values: missing_values) }

  if unanswered_keys.size > keys.size - minimum_present
    raise MissingAnswerValues.new \
            questionnaire_key: @questionnaire.key,
            values: @values,
            missing: unanswered_keys
  end
end
gender() click to toggle source

Public: Returns the Symbol describing the gender of the patient.

The symbol :unknown is returned when gender is not known.

# File lib/quby/answers/services/score_calculator.rb, line 206
def gender
  @patient.gender
end
max(*values) click to toggle source

Public: Max of values

values - an Array or list of Numerics

Returns the highest value of the given values

# File lib/quby/answers/services/score_calculator.rb, line 194
def max(*values)
  values.flatten.compact.max
end
mean(values, ignoring: [], minimum_present: 1) click to toggle source

Public: Gives mean of values

values - An Array of Numerics ignoring - An array of values to remove before taking the mean. minimum_present - return nil if less values than this are left after filtering

Returns the mean of the given values or nil if minimum_present is not met.

# File lib/quby/answers/services/score_calculator.rb, line 134
def mean(values, ignoring: [], minimum_present: 1)
  compacted_values = values.reject { |v| ignoring.include? v }
  return nil if compacted_values.blank? || compacted_values.length < minimum_present
  sum(compacted_values).to_f / compacted_values.length
end
mean_ignoring_nils(values) click to toggle source

Public: Gives mean of values, ignoring nil values

values - An Array of Numerics

Returns the mean of the given values

# File lib/quby/answers/services/score_calculator.rb, line 145
def mean_ignoring_nils(values)
  mean(values, ignoring: [nil])
end
mean_ignoring_nils_80_pct(values) click to toggle source

Public: Gives mean of values, ignoring nil values if >= 80% is filled in

values - An Array of Numerics

Returns the mean of the given values, or nil if less than 80% is present

# File lib/quby/answers/services/score_calculator.rb, line 154
def mean_ignoring_nils_80_pct(values)
  mean(values, ignoring: [nil], minimum_present: values.length * 0.8)
end
opencpu(package, function, parameters = {}) click to toggle source
# File lib/quby/answers/services/score_calculator.rb, line 229
def opencpu(package, function, parameters = {})
  client = ::OpenCPU.client
  client.execute(package, function, parameters)
end
referenced_values() click to toggle source
# File lib/quby/answers/services/score_calculator.rb, line 225
def referenced_values
  @values.keys.select { |key| @referenced_values.include? key }
end
respondent_type() click to toggle source

Public: Returns the type of the respondent

# File lib/quby/answers/services/score_calculator.rb, line 211
def respondent_type
  @respondent.type
end
score(key) click to toggle source

Public: Runs another score calculation or variable and returns its result

key - The Symbol of another score.

# File lib/quby/answers/services/score_calculator.rb, line 218
def score(key)
  fail "Score #{key.inspect} does not exist." unless @questionnaire.score_calculations.key? key

  calculation = @questionnaire.score_calculations.fetch(key)
  instance_eval(&calculation.calculation)
end
sum(values) click to toggle source

Public: Sums values

values - An Array of Numerics

Returns the sum of the given values

# File lib/quby/answers/services/score_calculator.rb, line 185
def sum(values)
  values.reduce(0, &:+)
end
sum_extrapolate(values, minimum_present) click to toggle source

Public: Sums values, extrapolating nils to be valued as the mean of the present values

values - An Array of Numerics minimum_answered - The minimum of values needed to be present, returns nil otherwise

Returns the sum of the given values, or nil if minimum_present is not met

# File lib/quby/answers/services/score_calculator.rb, line 164
def sum_extrapolate(values, minimum_present)
  return nil if values.reject(&:blank?).length < minimum_present
  mean = mean_ignoring_nils(values)
  values = values.map { |value| value ? value : mean }
  sum(values)
end
sum_extrapolate_80_pct(values) click to toggle source

Public: Sums values, extrapolating nils to be valued as the mean of the present values

values - An Array of Numerics

Returns the sum of the given values, or nil if less than 80% is present

# File lib/quby/answers/services/score_calculator.rb, line 176
def sum_extrapolate_80_pct(values)
  sum_extrapolate(values, values.length * 0.8)
end
table_lookup(table_key, parameters) click to toggle source
# File lib/quby/answers/services/score_calculator.rb, line 234
def table_lookup(table_key, parameters)
  @questionnaire.lookup_tables.fetch(table_key).lookup(parameters)
end
value(key) click to toggle source

Public: Get value for given question key

key - A key for which to return a value

Returns the value.

Raises MissingAnswerValues if the keys doesn't have a value.

# File lib/quby/answers/services/score_calculator.rb, line 100
def value(key)
  values(key).first
end
values(*keys) click to toggle source

Public: Get values for given question keys

*keys - A list or array of keys for which to return values

Returns an Array of values. Values are whatever they may be defined as, usually they are either Integers of Floats, but remember that no such restriction is placed. And for open questions the value will probably be a String. Returns hash of all values if no keys are given.

Raises MissingAnswerValues if one or more keys doesn't have a value.

# File lib/quby/answers/services/score_calculator.rb, line 65
def values(*keys)
  keys = keys.flatten(1).map(&:to_s)
  ensure_answer_values_for(keys)
  values_with_nils(keys)
end
values_with_nils(*keys) click to toggle source

Public: Get values for given question keys, or nil if the question is not filled in

*keys - A list of keys for which to return values

Returns an Array of values. Values are whatever they may be defined as, usually they are either Integers of Floats, but remember that no such restriction is placed. And for open questions the value will probably be a String. If the question is not filled in or the question key is unknown, nil will be returned for that question.

# File lib/quby/answers/services/score_calculator.rb, line 113
def values_with_nils(*keys)
  keys = keys.flatten(1).map(&:to_s)
  ensure_defined_question_keys(keys)
  ensure_no_duplicate_keys(keys)

  if keys.empty?
    remember_usage_of_value_keys(@values.keys)
    @values
  else
    remember_usage_of_value_keys(keys)
    @values.values_at(*keys)
  end
end
values_without_missings(*keys, minimum_present: 1, missing_values: []) click to toggle source

Public: Get values for given question keys removing any missing keys.

*keys - A list or array of keys for which to return values - required. *minimum_present - see Raises. *missing_values - extra values to consider missing.

Returns an Array of values. Values are whatever they may be defined as, usually they are either Integers of Floats, but remember that no such restriction is placed. And for open questions the value will probably be a String.

Raises MissingAnswerValues when less than minimum_present keys have a value.

# File lib/quby/answers/services/score_calculator.rb, line 83
def values_without_missings(*keys, minimum_present: 1, missing_values: [])
  keys = keys.flatten(1).map(&:to_s)
  fail ArgumentError, 'keys empty' unless keys.present?

  ensure_answer_values_for(keys, minimum_present: minimum_present, missing_values: missing_values)
  values_with_nils(keys).reject do |v|
    missing_value?(v, missing_values: missing_values)
  end
end

Private Instance Methods

ensure_defined_question_keys(keys) click to toggle source
# File lib/quby/answers/services/score_calculator.rb, line 267
def ensure_defined_question_keys(keys)
  unknown_keys = keys.reject { |key| @questionnaire.fields.key_in_use?(key) }

  if unknown_keys.present?
    fail UnknownFieldsReferenced, questionnaire_key: @questionnaire.key,
                                  unknown: unknown_keys
  end
end
ensure_no_duplicate_keys(keys) click to toggle source
# File lib/quby/answers/services/score_calculator.rb, line 276
def ensure_no_duplicate_keys(keys)
  fail ArgumentError, 'Key requested more than once' if keys.uniq!
end
missing_value?(value, missing_values: []) click to toggle source
# File lib/quby/answers/services/score_calculator.rb, line 280
def missing_value?(value, missing_values: [])
  value.blank? || missing_values.include?(value)
end
remember_usage_of_value_keys(keys) click to toggle source
# File lib/quby/answers/services/score_calculator.rb, line 263
def remember_usage_of_value_keys(keys)
  @referenced_values += keys
end
table_hash() click to toggle source
# File lib/quby/answers/services/score_calculator.rb, line 259
def table_hash
  @table_hash ||= {}.with_indifferent_access
end