diff --git a/app/catalogs/gad7_catalog.rb b/app/catalogs/gad7_catalog.rb
new file mode 100644
index 0000000..252d8b7
--- /dev/null
+++ b/app/catalogs/gad7_catalog.rb
@@ -0,0 +1,203 @@
+module Gad7Catalog
+ extend ActiveSupport::Concern
+
+ ### Over the last two weeks, how often have you been bothered by any of the following problems?
+
+ DEFINITION = [
+
+ ### Feeling nervous, anxious or on edge?
+ [{
+ # 0 Not at all
+ # 1 Several days
+ # 2 More than half the days
+ # 3 Nearly every day
+
+ name: :anxiety,
+ kind: :select,
+ inputs: [
+ {value: 0, label: "not_at_all", meta_label: "", helper: nil},
+ {value: 1, label: "several_days", meta_label: "", helper: nil},
+ {value: 2, label: "more_than_half_the_days", meta_label: "", helper: nil},
+ {value: 3, label: "nearly_every_day", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### Not being able to stop or control worrying?
+ [{
+ # 0 Not at all
+ # 1 Several days
+ # 2 More than half the days
+ # 3 Nearly every day
+
+ name: :uncontrollable_worry,
+ kind: :select,
+ inputs: [
+ {value: 0, label: "not_at_all", meta_label: "", helper: nil},
+ {value: 1, label: "several_days", meta_label: "", helper: nil},
+ {value: 2, label: "more_than_half_the_days", meta_label: "", helper: nil},
+ {value: 3, label: "nearly_every_day", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### Worrying too much about different things?
+ [{
+ # 0 Not at all
+ # 1 Several days
+ # 2 More than half the days
+ # 3 Nearly every day
+
+ name: :excessive_worry,
+ kind: :select,
+ inputs: [
+ {value: 0, label: "not_at_all", meta_label: "", helper: nil},
+ {value: 1, label: "several_days", meta_label: "", helper: nil},
+ {value: 2, label: "more_than_half_the_days", meta_label: "", helper: nil},
+ {value: 3, label: "nearly_every_day", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### Trouble relaxing?
+ [{
+ # 0 Not at all
+ # 1 Several days
+ # 2 More than half the days
+ # 3 Nearly every day
+
+ name: :trouble_relaxing,
+ kind: :select,
+ inputs: [
+ {value: 0, label: "not_at_all", meta_label: "", helper: nil},
+ {value: 1, label: "several_days", meta_label: "", helper: nil},
+ {value: 2, label: "more_than_half_the_days", meta_label: "", helper: nil},
+ {value: 3, label: "nearly_every_day", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### Being so restless that it is hard to sit still?
+ [{
+ # 0 Not at all
+ # 1 Several days
+ # 2 More than half the days
+ # 3 Nearly every day
+
+ name: :restlessness,
+ kind: :select,
+ inputs: [
+ {value: 0, label: "not_at_all", meta_label: "", helper: nil},
+ {value: 1, label: "several_days", meta_label: "", helper: nil},
+ {value: 2, label: "more_than_half_the_days", meta_label: "", helper: nil},
+ {value: 3, label: "nearly_every_day", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### Becoming easily annoyed or irritable?
+ [{
+ # 0 Not at all
+ # 1 Several days
+ # 2 More than half the days
+ # 3 Nearly every day
+
+ name: :irritability,
+ kind: :select,
+ inputs: [
+ {value: 0, label: "not_at_all", meta_label: "", helper: nil},
+ {value: 1, label: "several_days", meta_label: "", helper: nil},
+ {value: 2, label: "more_than_half_the_days", meta_label: "", helper: nil},
+ {value: 3, label: "nearly_every_day", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### Feeling afraid as if something awful might happen?
+ [{
+ # 0 Not at all
+ # 1 Several days
+ # 2 More than half the days
+ # 3 Nearly every day
+
+ name: :impending_doom,
+ kind: :select,
+ inputs: [
+ {value: 0, label: "not_at_all", meta_label: "", helper: nil},
+ {value: 1, label: "several_days", meta_label: "", helper: nil},
+ {value: 2, label: "more_than_half_the_days", meta_label: "", helper: nil},
+ {value: 3, label: "nearly_every_day", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ]
+
+ SCORE_COMPONENTS = %i( anxiety uncontrollable_worry excessive_worry trouble_relaxing restlessness irritability impending_doom )
+ QUESTIONS = DEFINITION.map{|questions| questions.map{|question| question[:name] }}.flatten
+ COMPLICATIONS = DEFINITION[4].map{|question| question[:name] }.flatten
+
+ included do |base_class|
+ validate :gad7_response_ranges
+ def gad7_response_ranges
+ ranges = [
+ [:anxiety, [nil,*0..3]],
+ [:uncontrollable_worry, [nil,*0..3]],
+ [:excessive_worry, [nil,*0..3]],
+ [:trouble_relaxing, [nil,*0..3]],
+ [:restlessness, [nil,*0..3]],
+ [:irritability, [nil,*0..3]],
+ [:impending_doom, [nil,*0..3]],
+ ]
+
+ ranges.each do |range|
+ response = gad7_responses.detect{|r| r.name.to_sym == range[0]}
+ if response and not range[1].include?(response.value)
+ # TODO add catalog namespace here
+ # self.errors.messages ||= {}
+ # self.errors.messages[range[0]] = "no_within_allowed_values"
+ self.errors.add range[0], "not_within_allowed_values"
+ end
+ end
+
+ end
+
+ # validate :hbi_response_booleans
+ # def hbi_response_booleans
+ # HbiCatalog::COMPLICATIONS.each do |name|
+ # response = hbi_responses.detect{|r| r.name.to_sym == name}
+
+ # if response and not [0,1].include? response.value.to_i
+ # # self.errors.messages ||= {}
+ # # self.errors.messages[name.to_sym] = "must_be_boolean"
+ # self.errors.add name.to_sym, "must_be_boolean"
+ # end
+ # end
+ # end
+
+ end
+
+ def gad7_responses
+ responses.select{|r| r.catalog == "gad7"}
+ end
+
+ # def valid_hbi_entry?
+ # return false unless last_6_entries.count == 6
+ # !last_6_entries.map{|e| e.filled_hbi_entry?}.include?(false)
+ # end
+ def filled_gad7_entry?
+ valid_responses = gad7_responses.reduce([]) do |accu, response|
+ accu << response.name.to_sym if response.name and response.value.present?
+ accu
+ end
+ (QUESTIONS-valid_responses) == []
+ end
+
+ def complete_gad7_entry?
+ filled_gad7_entry?
+ end
+
+ # def setup_hbi_scoring
+ # end
+
+ def gad7_complications_score
+ COMPLICATIONS.reduce(0) do |sum, question_name|
+ sum + (self.send("gad7_#{question_name}").to_i)
+ end.to_f
+ end
+
+
+end
\ No newline at end of file
diff --git a/app/catalogs/sf20_catalog.rb b/app/catalogs/sf20_catalog.rb
new file mode 100644
index 0000000..9504f6b
--- /dev/null
+++ b/app/catalogs/sf20_catalog.rb
@@ -0,0 +1,573 @@
+module Sf20Catalog
+ extend ActiveSupport::Concern
+
+ DEFINITION = [
+
+ ### SECTION A
+
+ ### In general, would you say your health is:
+ [{
+ # 1 Excellent
+ # 2 Very good
+ # 3 Good
+ # 4 Fair
+ # 5 Poor
+
+ name: :general_health,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "excellent", meta_label: "", helper: nil},
+ {value: 2, label: "very_good", meta_label: "", helper: nil},
+ {value: 3, label: "good", meta_label: "", helper: nil},
+ {value: 4, label: "fair", meta_label: "", helper: nil},
+ {value: 5, label: "poor", meta_label: "", helper: nil},
+ ]
+ }],
+
+
+ ### SECTION B
+ ### For how long (if at all) has your *health limited you* in *each* of the following activities?
+
+ ### The kinds or amounts of *vigorous* activities you can do, like lifting heavy objects, running or participating in strenuous sports.
+ [{
+ # 1 Limited for more than 3 months
+ # 2 Limited for 3 months or less
+ # 3 Not limited at all
+
+ name: :limit_vigorous_activity,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "limited_more_than_3_months", meta_label: "", helper: nil},
+ {value: 2, label: "limited_3_months_or_less", meta_label: "", helper: nil},
+ {value: 3, label: "not_limited_at_all", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### The kinds or amounts of moderate activities you can do, like moving a table, carrying groceries or bowling.
+ [{
+ # 1 Limited for more than 3 months
+ # 2 Limited for 3 months or less
+ # 3 Not limited at all
+
+ name: :limit_moderate_activity,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "limited_more_than_3_months", meta_label: "", helper: nil},
+ {value: 2, label: "limited_3_months_or_less", meta_label: "", helper: nil},
+ {value: 3, label: "not_limited_at_all", meta_label: "", helper: nil},
+ ]
+ }],
+
+
+ ### Walking uphill or climbing a few flights of stairs.
+ [{
+ # 1 Limited for more than 3 months
+ # 2 Limited for 3 months or less
+ # 3 Not limited at all
+
+ name: :limit_climbing_stairs,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "limited_more_than_3_months", meta_label: "", helper: nil},
+ {value: 2, label: "limited_3_months_or_less", meta_label: "", helper: nil},
+ {value: 3, label: "not_limited_at_all", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### Bending, lifting, or stooping?
+ [{
+ # 1 Limited for more than 3 months
+ # 2 Limited for 3 months or less
+ # 3 Not limited at all
+
+ name: :limit_bending,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "limited_more_than_3_months", meta_label: "", helper: nil},
+ {value: 2, label: "limited_3_months_or_less", meta_label: "", helper: nil},
+ {value: 3, label: "not_limited_at_all", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### Walking one block?
+ [{
+ # 1 Limited for more than 3 months
+ # 2 Limited for 3 months or less
+ # 3 Not limited at all
+
+ name: :limit_walking,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "limited_more_than_3_months", meta_label: "", helper: nil},
+ {value: 2, label: "limited_3_months_or_less", meta_label: "", helper: nil},
+ {value: 3, label: "not_limited_at_all", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### Eating, dressing, bathing, or using the toilet?
+ [{
+ # 1 Limited for more than 3 months
+ # 2 Limited for 3 months or less
+ # 3 Not limited at all
+
+ name: :limit_basic_activity,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "limited_more_than_3_months", meta_label: "", helper: nil},
+ {value: 2, label: "limited_3_months_or_less", meta_label: "", helper: nil},
+ {value: 3, label: "not_limited_at_all", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### SECTION C
+ ### How much *bodily* pain have you had *during the past 4 weeks*?
+
+ [{
+ # 1 None
+ # 2 Very mild
+ # 3 Mild
+ # 4 Moderate
+ # 5 Severe
+ # 6 Very severe
+
+ name: :bodily_pain,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "none", meta_label: "", helper: nil},
+ {value: 2, label: "very_mild", meta_label: "", helper: nil},
+ {value: 3, label: "mild", meta_label: "", helper: nil},
+ {value: 4, label: "moderate", meta_label: "", helper: nil},
+ {value: 5, label: "severe", meta_label: "", helper: nil},
+ {value: 6, label: "very_severe", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### SECTION D
+ ### Does your health *keep* you from working at a job, doing work around the house, or going to school?
+ [{
+ # 1 Yes, for more than three months
+ # 2 Yes, for three months or less
+ # 3 No
+
+ name: :prevent_working,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "yes_more_than_3_months", meta_label: "", helper: nil},
+ {value: 2, label: "yes_3_months_or_less", meta_label: "", helper: nil},
+ {value: 3, label: "no", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### SECTION E
+ ### Have you been unable to do *certain kinds or amounts* of work, housework, or schoolwork because of your health?
+ [{
+ # 1 Yes, for more than three months
+ # 2 Yes, for three months or less
+ # 3 No
+
+ name: :prevent_certain_kinds_work,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "yes_more_than_3_months", meta_label: "", helper: nil},
+ {value: 2, label: "yes_3_months_or_less", meta_label: "", helper: nil},
+ {value: 3, label: "no", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### SECTION F
+ ### For *each* of the following questions, please mark the circle for the *one* answer that comes *closest* to the way you have been feeling *during the past month*.
+
+ ### How much of the time, during the past month, has your *health limited your social activities* (like visiting with friends or close relatives)?
+ [{
+ # 1 All of the time
+ # 2 Most of the time
+ # 3 A good bit of the time
+ # 4 Some of the time
+ # 5 A little of the time
+ # 6 None of the time
+
+ name: :limit_social_activities,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "all_of_the_time", meta_label: "", helper: nil},
+ {value: 2, label: "most_of_the_time", meta_label: "", helper: nil},
+ {value: 3, label: "a_good_bit_of_the_time", meta_label: "", helper: nil},
+ {value: 4, label: "some_of_the_time", meta_label: "", helper: nil},
+ {value: 5, label: "a_little_of_the_time", meta_label: "", helper: nil},
+ {value: 6, label: "none_of_the_time", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### How much of the time, during the past month, have you been a very nervous person?
+ [{
+ # 1 All of the time
+ # 2 Most of the time
+ # 3 A good bit of the time
+ # 4 Some of the time
+ # 5 A little of the time
+ # 6 None of the time
+
+ name: :nervousness,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "all_of_the_time", meta_label: "", helper: nil},
+ {value: 2, label: "most_of_the_time", meta_label: "", helper: nil},
+ {value: 3, label: "a_good_bit_of_the_time", meta_label: "", helper: nil},
+ {value: 4, label: "some_of_the_time", meta_label: "", helper: nil},
+ {value: 5, label: "a_little_of_the_time", meta_label: "", helper: nil},
+ {value: 6, label: "none_of_the_time", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### During the past month, how much of the time have you felt calm and peaceful?
+ [{
+ # 1 All of the time
+ # 2 Most of the time
+ # 3 A good bit of the time
+ # 4 Some of the time
+ # 5 A little of the time
+ # 6 None of the time
+
+ name: :calmness,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "all_of_the_time", meta_label: "", helper: nil},
+ {value: 2, label: "most_of_the_time", meta_label: "", helper: nil},
+ {value: 3, label: "a_good_bit_of_the_time", meta_label: "", helper: nil},
+ {value: 4, label: "some_of_the_time", meta_label: "", helper: nil},
+ {value: 5, label: "a_little_of_the_time", meta_label: "", helper: nil},
+ {value: 6, label: "none_of_the_time", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### How much of the time, during the past month, have you felt downhearted and blue?
+ [{
+ # 1 All of the time
+ # 2 Most of the time
+ # 3 A good bit of the time
+ # 4 Some of the time
+ # 5 A little of the time
+ # 6 None of the time
+
+ name: :downheartedness,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "all_of_the_time", meta_label: "", helper: nil},
+ {value: 2, label: "most_of_the_time", meta_label: "", helper: nil},
+ {value: 3, label: "a_good_bit_of_the_time", meta_label: "", helper: nil},
+ {value: 4, label: "some_of_the_time", meta_label: "", helper: nil},
+ {value: 5, label: "a_little_of_the_time", meta_label: "", helper: nil},
+ {value: 6, label: "none_of_the_time", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### During the past month, how much of the time have you been a happy person?
+ [{
+ # 1 All of the time
+ # 2 Most of the time
+ # 3 A good bit of the time
+ # 4 Some of the time
+ # 5 A little of the time
+ # 6 None of the time
+
+ name: :happiness,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "all_of_the_time", meta_label: "", helper: nil},
+ {value: 2, label: "most_of_the_time", meta_label: "", helper: nil},
+ {value: 3, label: "a_good_bit_of_the_time", meta_label: "", helper: nil},
+ {value: 4, label: "some_of_the_time", meta_label: "", helper: nil},
+ {value: 5, label: "a_little_of_the_time", meta_label: "", helper: nil},
+ {value: 6, label: "none_of_the_time", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### How often, during the past month, have you felt so down in the dumps that nothing could cheer you up?
+ [{
+ # 1 All of the time
+ # 2 Most of the time
+ # 3 A good bit of the time
+ # 4 Some of the time
+ # 5 A little of the time
+ # 6 None of the time
+
+ name: :despair,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "all_of_the_time", meta_label: "", helper: nil},
+ {value: 2, label: "most_of_the_time", meta_label: "", helper: nil},
+ {value: 3, label: "a_good_bit_of_the_time", meta_label: "", helper: nil},
+ {value: 4, label: "some_of_the_time", meta_label: "", helper: nil},
+ {value: 5, label: "a_little_of_the_time", meta_label: "", helper: nil},
+ {value: 6, label: "none_of_the_time", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### SECTION G
+ ### Please mark the circle that *best* describes whether *each* of the following statements is *true* or *false* for you.
+
+ ### I am somewhat ill.
+ [{
+ # 1 Definitely true
+ # 2 Mostly true
+ # 3 Not sure
+ # 4 Mostly false
+ # 5 Definitely false
+
+ name: :somewhat_ill,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "definitely_true", meta_label: "", helper: nil},
+ {value: 2, label: "mostly_true", meta_label: "", helper: nil},
+ {value: 3, label: "not_sure", meta_label: "", helper: nil},
+ {value: 4, label: "mostly_false", meta_label: "", helper: nil},
+ {value: 5, label: "definitely_false", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### I am as healthy as anybody I know.
+ [{
+ # 1 Definitely true
+ # 2 Mostly true
+ # 3 Not sure
+ # 4 Mostly false
+ # 5 Definitely false
+
+ name: :healthy_as_anybody,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "definitely_true", meta_label: "", helper: nil},
+ {value: 2, label: "mostly_true", meta_label: "", helper: nil},
+ {value: 3, label: "not_sure", meta_label: "", helper: nil},
+ {value: 4, label: "mostly_false", meta_label: "", helper: nil},
+ {value: 5, label: "definitely_false", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### My health is excellent.
+ [{
+ # 1 Definitely true
+ # 2 Mostly true
+ # 3 Not sure
+ # 4 Mostly false
+ # 5 Definitely false
+
+ name: :excellent_health,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "definitely_true", meta_label: "", helper: nil},
+ {value: 2, label: "mostly_true", meta_label: "", helper: nil},
+ {value: 3, label: "not_sure", meta_label: "", helper: nil},
+ {value: 4, label: "mostly_false", meta_label: "", helper: nil},
+ {value: 5, label: "definitely_false", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ### I have been feeling bad lately.
+ [{
+ # 1 Definitely true
+ # 2 Mostly true
+ # 3 Not sure
+ # 4 Mostly false
+ # 5 Definitely false
+
+ name: :feeling_bad_lately,
+ kind: :select,
+ inputs: [
+ {value: 1, label: "definitely_true", meta_label: "", helper: nil},
+ {value: 2, label: "mostly_true", meta_label: "", helper: nil},
+ {value: 3, label: "not_sure", meta_label: "", helper: nil},
+ {value: 4, label: "mostly_false", meta_label: "", helper: nil},
+ {value: 5, label: "definitely_false", meta_label: "", helper: nil},
+ ]
+ }],
+
+ ]
+
+ QUESTIONS = %i( general_health limit_vigorous_activity limit_moderate_activity limit_climbing_stairs limit_bending limit_walking limit_basic_activity bodily_pain prevent_working prevent_certain_kinds_work limit_social_activities nervousness calmness downheartedness happiness despair somewhat_ill healthy_as_anybody excellent_health feeling_bad_lately )
+ SCORE_COMPONENTS = %i( section_a section_b section_c section_d section_e section_f section_g)
+ SCORE_COMPONENT_QUESTIONS = [1,6,1,1,1,6,4]
+ QUESTIONS = DEFINITION.map{|questions| questions.map{|question| question[:name] }}.flatten
+ COMPLICATIONS = DEFINITION[4].map{|question| question[:name] }.flatten
+ SECTION_SCALE_MAX = 3
+
+ included do |base_class|
+ validate :sf20_response_ranges
+ def sf20_response_ranges
+ ranges = [
+ [:general_health, [nil,*1..5]],
+ [:limit_vigorous_activity, [nil,*1..3]],
+ [:limit_moderate_activity, [nil,*1..3]],
+ [:limit_climbing_stairs, [nil,*1..3]],
+ [:limit_bending, [nil,*1..3]],
+ [:limit_walking, [nil,*1..3]],
+ [:limit_basic_activity, [nil,*1..3]],
+ [:bodily_pain, [nil,*1..6]],
+ [:prevent_working, [nil,*1..3]],
+ [:prevent_certain_kinds_work, [nil,*1..3]],
+ [:limit_social_activities, [nil,*1..6]],
+ [:nervousness, [nil,*1..6]],
+ [:calmness, [nil,*1..6]],
+ [:downheartedness, [nil,*1..6]],
+ [:happiness, [nil,*1..6]],
+ [:despair, [nil,*1..6]],
+ [:somewhat_ill, [nil,*1..5]],
+ [:healthy_as_anybody, [nil,*1..5]],
+ [:excellent_health, [nil,*1..5]],
+ [:feeling_bad_lately, [nil,*1..5]],
+ ]
+
+ ranges.each do |range|
+ response = sf20_responses.detect{|r| r.name.to_sym == range[0]}
+ if response and not range[1].include?(response.value)
+ # TODO add catalog namespace here
+ # self.errors.messages ||= {}
+ # self.errors.messages[range[0]] = "no_within_allowed_values"
+ self.errors.add range[0], "not_within_allowed_values"
+ end
+ end
+
+ end
+
+ # validate :hbi_response_booleans
+ # def hbi_response_booleans
+ # HbiCatalog::COMPLICATIONS.each do |name|
+ # response = hbi_responses.detect{|r| r.name.to_sym == name}
+
+ # if response and not [0,1].include? response.value.to_i
+ # # self.errors.messages ||= {}
+ # # self.errors.messages[name.to_sym] = "must_be_boolean"
+ # self.errors.add name.to_sym, "must_be_boolean"
+ # end
+ # end
+ # end
+
+ end
+
+ def sf20_responses
+ responses.select{|r| r.catalog == "sf20"}
+ end
+
+ # def valid_hbi_entry?
+ # return false unless last_6_entries.count == 6
+ # !last_6_entries.map{|e| e.filled_hbi_entry?}.include?(false)
+ # end
+ def filled_sf20_entry?
+ valid_responses = sf20_responses.reduce([]) do |accu, response|
+ accu << response.name.to_sym if response.name and response.value.present?
+ accu
+ end
+ (QUESTIONS-valid_responses) == []
+ end
+
+ def complete_sf20_entry?
+ filled_sf20_entry?
+ end
+
+ # def setup_hbi_scoring
+ # end
+
+ def invert(n, domain_min, domain_max)
+ domain_max + domain_min - n
+ end
+
+ def questions_for_section(section_name)
+ questions = QUESTIONS
+ SCORE_COMPONENT_QUESTIONS.reduce([]) do |memo,length|
+ section = questions[0..length-1]
+ memo << section
+ questions = questions - section
+ memo
+ end[('a'..'z').to_a.index(section_name)]
+ end
+
+ def scale_score(score, domain_min, domain_max, range_max)
+ domain_extent = domain_max - domain_min
+ range_max * (score - domain_min) / domain_extent
+ end
+
+ def sf20_section_a_score
+ general_health_recoding = {1 => 1, 2 => 1.64, 3 => 2.59, 4 => 4.01, 5 => 5} # they made the items not equidistant
+
+ questions = questions_for_section("a")
+ avg = questions.reduce(0) do |sum, name|
+ sum + general_health_recoding[self.send("sf20_#{name}").round] # round because we need to use it as a hash key
+ end / questions.length
+
+ scaled = scale_score(avg, 1, 5, SECTION_SCALE_MAX)
+ scaled.round
+ end
+
+ def sf20_section_b_score
+ questions = questions_for_section("b")
+ avg = questions.reduce(0) do |sum, name|
+ sum + self.send("sf20_#{name}")
+ end / questions.length
+
+ scaled = scale_score(avg, 1, 3, SECTION_SCALE_MAX)
+ scaled.round
+ end
+
+ def sf20_section_c_score
+ questions = questions_for_section("c")
+ avg = questions.reduce(0) do |sum, name|
+ sum + self.send("sf20_#{name}")
+ end / questions.length
+
+ scaled = scale_score(avg, 1, 6, SECTION_SCALE_MAX)
+ scaled.round
+ end
+
+ def sf20_section_d_score
+ questions = questions_for_section("d")
+ avg = questions.reduce(0) do |sum, name|
+ sum + invert(self.send("sf20_#{name}"), 1, 3)
+ end / questions.length
+
+ scaled = scale_score(avg, 1, 3, SECTION_SCALE_MAX)
+ scaled.round
+ end
+
+ def sf20_section_e_score
+ questions = questions_for_section("e")
+ avg = questions.reduce(0) do |sum, name|
+ sum + invert(self.send("sf20_#{name}"), 1, 3)
+ end / questions.length
+
+ scaled = scale_score(avg, 1, 3, SECTION_SCALE_MAX)
+ scaled.round
+ end
+
+ def sf20_section_f_score
+ questions_to_invert = [:calmness, :happiness]
+
+ questions = questions_for_section("f")
+ avg = questions.reduce(0) do |sum, name|
+ if questions_to_invert.include?(name)
+ sum + invert(self.send("sf20_#{name}"), 1, 6)
+ else
+ sum + self.send("sf20_#{name}")
+ end
+ end / questions.length
+
+ scaled = scale_score(avg, 1, 6, SECTION_SCALE_MAX)
+ scaled.round
+ end
+
+ def sf20_section_g_score
+ questions_to_invert = [:healthy_as_anybody, :excellent_health]
+
+ questions = questions_for_section("g")
+ avg = questions.reduce(0) do |sum, name|
+ if questions_to_invert.include?(name)
+ sum + invert(self.send("sf20_#{name}"), 1, 5)
+ else
+ sum + self.send("sf20_#{name}")
+ end
+ end / questions.length
+
+ scaled = scale_score(avg, 1, 5, SECTION_SCALE_MAX)
+ scaled.round
+ end
+
+end
\ No newline at end of file
diff --git a/config/initializers/catalog_conditions.rb b/config/initializers/catalog_conditions.rb
index 935b339..d3fc291 100644
--- a/config/initializers/catalog_conditions.rb
+++ b/config/initializers/catalog_conditions.rb
@@ -1,5 +1,6 @@
CATALOG_CONDITIONS = {
"Crohn's disease" => "hbi",
"Rheumatoid arthritis" => "rapid3",
- "Depression" => "phq9"
+ "Depression" => "phq9",
+ "Generalized anxiety disorder" => "gad7",
}
\ No newline at end of file
diff --git a/config/locales/en/catalogs/gad7.yml b/config/locales/en/catalogs/gad7.yml
new file mode 100644
index 0000000..7ec48f7
--- /dev/null
+++ b/config/locales/en/catalogs/gad7.yml
@@ -0,0 +1,36 @@
+catalog_description: "Generalized Anxiety Disorder Assessment (GAD-7)"
+
+section_1_description: "Feeling nervous, anxious or on edge?"
+section_1_prompt: "Feeling nervous, anxious or on edge?"
+section_1_header: "Over the past two weeks, how often have you been bothered by ..."
+anxiety: "Anxiety"
+
+section_2_description: "Not being able to stop or control worrying?"
+section_2_prompt: "Not being able to stop or control worrying?"
+section_2_header: "Over the past two weeks, how often have you been bothered by ..."
+uncontrollable_worry: "Uncontrollable worry"
+
+section_3_description: "Worrying too much about different things?"
+section_3_prompt: "Worrying too much about different things?"
+section_3_header: "Over the past two weeks, how often have you been bothered by ..."
+excessive_worry: "Excessive worry"
+
+section_4_description: "Trouble relaxing?"
+section_4_prompt: "Trouble relaxing?"
+section_4_header: "Over the past two weeks, how often have you been bothered by ..."
+trouble_relaxing: "Trouble relaxing"
+
+section_5_description: "Being so restless that it is hard to sit still?"
+section_5_prompt: "Being so restless that it is hard to sit still?"
+section_5_header: "Over the past two weeks, how often have you been bothered by ..."
+restlessness: "Restlessness"
+
+section_6_description: "Becoming easily annoyed or irritable?"
+section_6_prompt: "Becoming easily annoyed or irritable?"
+section_6_header: "Over the past two weeks, how often have you been bothered by ..."
+irritability: "Irritability"
+
+section_7_description: "Feeling afraid as if something awful might happen?"
+section_7_prompt: "Feeling afraid as if something awful might happen?"
+section_7_header: "Over the past two weeks, how often have you been bothered by ..."
+impending_doom: "A sense of impending doom"
diff --git a/config/locales/en/catalogs/sf20.yml b/config/locales/en/catalogs/sf20.yml
new file mode 100644
index 0000000..c4d7b0e
--- /dev/null
+++ b/config/locales/en/catalogs/sf20.yml
@@ -0,0 +1,102 @@
+catalog_description: "Medical Outcomes Study (MOS) 20-Item Short Form Survey (SF-20)"
+
+section_1_description: "In general, would you say your health is:"
+section_1_prompt: "In general, would you say your health is:"
+section_1_header: ""
+general_health: "General health"
+
+section_2_description: "The kinds or amounts of vigorous activities you can do, like lifting heavy objects, running or participating in strenuous sports?"
+section_2_prompt: "The kinds or amounts of vigorous activities you can do, like lifting heavy objects, running or participating in strenuous sports?"
+section_2_header: "For how long (if at all) has your health limited you in ..."
+limit_vigorous_activity: "Limits on vigorous activity"
+
+section_3_description: "The kinds or amounts of moderate activities you can do, like moving a table, carrying groceries or bowling?"
+section_3_prompt: "The kinds or amounts of moderate activities you can do, like moving a table, carrying groceries or bowling?"
+section_3_header: "For how long (if at all) has your health limited you in ..."
+limit_moderate_activity: "Limits on moderate activity"
+
+section_4_description: "Walking uphill or climbing a few flights of stairs?"
+section_4_prompt: "Walking uphill or climbing a few flights of stairs?"
+section_4_header: "For how long (if at all) has your health limited you in ..."
+limit_climbing_stairs: "Limits on climbing stairs"
+
+section_5_description: "Bending, lifting, or stooping?"
+section_5_prompt: "Bending, lifting, or stooping?"
+section_5_header: "For how long (if at all) has your health limited you in ..."
+limit_bending: "Limits on bending"
+
+section_6_description: "Walking one block?"
+section_6_prompt: "Walking one block?"
+section_6_header: "For how long (if at all) has your health limited you in ..."
+limit_walking: "Limits on walking"
+
+section_7_description: "Eating, dressing, bathing, or using the toilet?"
+section_7_prompt: "Eating, dressing, bathing, or using the toilet?"
+section_7_header: "For how long (if at all) has your health limited you in ..."
+limit_basic_activity: "Limits on basic activities"
+
+section_8_description: "How much bodily pain have you had during the past 4 weeks?"
+section_8_prompt: "How much bodily pain have you had during the past 4 weeks?"
+section_8_header: ""
+bodily_pain: "Bodily pain"
+
+section_9_description: "Does your health keep you from working at a job, doing work around the house, or going to school?"
+section_9_prompt: "Does your health keep you from working at a job, doing work around the house, or going to school?"
+section_9_header: ""
+prevent_working: "Interfering with working"
+
+section_10_description: "Have you been unable to do certain kinds or amounts of work, housework, or schoolwork because of your health?"
+section_10_prompt: "Have you been unable to do certain kinds or amounts of work, housework, or schoolwork because of your health?"
+section_10_header: ""
+prevent_certain_kinds_work: "Interfering with certain kinds of work"
+
+section_11_description: "How much of the time, during the past month, has your health limited your social activities (like visiting with friends or close relatives)?"
+section_11_prompt: "How much of the time, during the past month, has your health limited your social activities (like visiting with friends or close relatives)?"
+section_11_header: "Which answer comes closest to the way you have been feeling during the past month?"
+limit_social_activities: "Limits on social activities"
+
+section_12_description: "How much of the time, during the past month, have you been a very nervous person?"
+section_12_prompt: "How much of the time, during the past month, have you been a very nervous person?"
+section_12_header: "Which answer comes closest to the way you have been feeling during the past month?"
+nervousness: "Nervousness"
+
+section_13_description: "During the past month, how much of the time have you felt calm and peaceful?"
+section_13_prompt: "During the past month, how much of the time have you felt calm and peaceful?"
+section_13_header: "Which answer comes closest to the way you have been feeling during the past month?"
+calmness: "Calmness"
+
+section_14_description: "How much of the time, during the past month, have you felt downhearted and blue?"
+section_14_prompt: "How much of the time, during the past month, have you felt downhearted and blue?"
+section_14_header: "Which answer comes closest to the way you have been feeling during the past month?"
+downheartedness: "Downheartedness"
+
+section_15_description: "During the past month, how much of the time have you been a happy person?"
+section_15_prompt: "During the past month, how much of the time have you been a happy person?"
+section_15_header: "Which answer comes closest to the way you have been feeling during the past month?"
+happiness: "Happiness"
+
+section_16_description: "How often, during the past month, have you felt so down in the dumps that nothing could cheer you up?"
+section_16_prompt: "How often, during the past month, have you felt so down in the dumps that nothing could cheer you up?"
+section_16_header: "Which answer comes closest to the way you have been feeling during the past month?"
+despair: "Despair"
+
+section_17_description: "I am somewhat ill."
+section_17_prompt: "I am somewhat ill."
+section_17_header: "Is this statement true or false for you?"
+somewhat_ill: "Somewhat ill"
+
+section_18_description: "I am as healthy as anybody I know."
+section_18_prompt: "I am as healthy as anybody I know."
+section_18_header: "Is this statement true or false for you?"
+healthy_as_anybody: "Healthy as anyone I know"
+
+section_19_description: "My health is excellent."
+section_19_prompt: "My health is excellent."
+section_19_header: "Is this statement true or false for you?"
+excellent_health: "Excellent health"
+
+section_20_description: "I have been feeling bad lately."
+section_20_prompt: "I have been feeling bad lately."
+section_20_header: "Is this statement true or false for you?"
+feeling_bad_lately: "Feeling bad lately"
+
diff --git a/config/locales/en/en_base.yml b/config/locales/en/en_base.yml
index 220177f..3e965b4 100644
--- a/config/locales/en/en_base.yml
+++ b/config/locales/en/en_base.yml
@@ -268,6 +268,11 @@ en:
severe: "Severe"
extreme: "Extreme"
+ excellent: "Excellent"
+ very_good: "Very good"
+ good: "Good"
+ fair: "Fair"
+
dubious: "Dubious"
definite: "Definite"
definite_and_tender: "Definite and tender"
@@ -277,6 +282,27 @@ en:
more_than_half_the_days: "More than half the days"
nearly_every_day: "Nearly every day"
+ limited_more_than_3_months: "Limited for more than 3 months"
+ limited_3_months_or_less: "Limited for 3 months or less"
+ not_limited_at_all: "Not limited at all"
+
+ yes_more_than_3_months: "Yes, for more than 3 months"
+ yes_3_months_or_less: "Yes, for 3 months or less"
+ no: "No"
+
+ all_of_the_time: "All of the time"
+ most_of_the_time: "Most of the time"
+ a_good_bit_of_the_time: "A good bit of the time"
+ some_of_the_time: "Some of the time"
+ a_little_of_the_time: "A little of the time"
+ none_of_the_time: "None of the time"
+
+ definitely_true: "Definitely true"
+ mostly_true: "Mostly true"
+ not_sure: "Not sure"
+ mostly_false: "Mostly false"
+ definitely_false: "Definitely false"
+
helpers:
basic_0: "Not active"
basic_1: "Not very active"
diff --git a/spec/catalogs/sf20_spec.rb b/spec/catalogs/sf20_spec.rb
new file mode 100644
index 0000000..1387e83
--- /dev/null
+++ b/spec/catalogs/sf20_spec.rb
@@ -0,0 +1,111 @@
+require 'spec_helper'
+
+describe Sf20Catalog do
+
+ describe "custom score thing" do
+ let(:user) { create :user }
+ let!(:entry) { create :sf20_entry }
+ it "outputs a custom score for section a" do
+ expect(entry.sf20_section_a_score).to eq 0
+ end
+ it "outputs a custom score for section b" do
+ expect(entry.sf20_section_b_score).to eq 1
+ end
+ it "outputs a custom score for section c" do
+ expect(entry.sf20_section_c_score).to eq 3
+ end
+ it "outputs a custom score for section d" do
+ expect(entry.sf20_section_d_score).to eq 3
+ end
+ it "outputs a custom score for section e" do
+ expect(entry.sf20_section_e_score).to eq 3
+ end
+ it "outputs a custom score for section f" do
+ expect(entry.sf20_section_f_score).to eq 1
+ end
+ it "outputs a custom score for section g" do
+ expect(entry.sf20_section_g_score).to eq 3
+ end
+ end
+
+ describe "#score" do
+ let(:user) { create :user }
+
+ describe "General" do
+ let!(:entry) { create :sf20_entry }
+
+ it "should have a score queue in resque" do
+ expect(Entry).to have_queue_size_of(1)
+ end
+
+ it "should validate responses" do
+ # Range
+ range_response = entry.responses.detect{|q| q.name == "calmness"}
+ range_response.value = 999
+ expect(entry).to be_invalid
+ range_response.value = 1
+ expect(entry).to be_valid
+
+ # Nils
+ range_response.value = nil
+ expect(entry).to be_valid
+
+ # # Boolean
+ # boolean_response = entry.responses.detect{|q| q.name == "complication_abscess"}
+ # boolean_response.value = 2
+ # expect(entry).to be_invalid
+ # boolean_response.value = 1
+ # expect(entry).to be_valid
+ end
+
+ it "isn't complete if nils responses are present" do
+ response = entry.responses.detect{|q| q.name == "general_health"}
+ response.value = nil
+ expect(entry.complete_sf20_entry?).to be_false
+ end
+ end
+
+ # describe "Scoring" do
+ # let(:entry) { create :hbi_entry, user: user }
+ # it "has a score if all responses are present" do
+ # expect(entry.responses.count).to eq HbiCatalog::QUESTIONS.count
+ # expect(entry.hbi_score).to be > 0
+ # end
+
+ # it "reverts to -1 (incomplete) if any responses are removed" do
+ # entry.responses.delete entry.responses.first
+ # with_resque{ entry.save }; entry.reload
+
+ # expect(entry.responses.count).to be < HbiCatalog::QUESTIONS.count
+ # expect(entry.reload.hbi_score).to eql -1.0
+ # end
+ # end
+
+ # describe "Calculations" do
+ # let(:entry) { build :hbi_entry, user: user }
+ # it "sample entry scores match calculated scores" do
+ # entry.responses << build(:response, {catalog: "hbi", name: :general_wellbeing , value: 1})
+ # entry.responses << build(:response, {catalog: "hbi", name: :ab_pain , value: 2})
+ # entry.responses << build(:response, {catalog: "hbi", name: :stools , value: 3})
+ # entry.responses << build(:response, {catalog: "hbi", name: :ab_mass , value: 0})
+
+ # entry.responses << build(:response, {catalog: "hbi", name: :complication_arthralgia , value: 0})
+ # entry.responses << build(:response, {catalog: "hbi", name: :complication_uveitis , value: 0})
+ # entry.responses << build(:response, {catalog: "hbi", name: :complication_erythema_nodosum , value: 0})
+ # entry.responses << build(:response, {catalog: "hbi", name: :complication_aphthous_ulcers , value: 1})
+ # entry.responses << build(:response, {catalog: "hbi", name: :complication_anal_fissure , value: 0})
+ # entry.responses << build(:response, {catalog: "hbi", name: :complication_fistula , value: 0})
+ # entry.responses << build(:response, {catalog: "hbi", name: :complication_abscess , value: 1})
+
+ # entry.save
+ # Entry.perform entry.id, false
+ # entry.reload
+
+ # expect(entry.hbi_complications_score).to eql 2.0
+ # expect(entry.hbi_score).to eql 8.0
+ # end
+ # end
+
+
+ end
+end
diff --git a/spec/factories/entry_factory.rb b/spec/factories/entry_factory.rb
index 191b146..8bfbdee 100644
--- a/spec/factories/entry_factory.rb
+++ b/spec/factories/entry_factory.rb
@@ -77,6 +77,55 @@ def random_boolean
end
end
+FactoryGirl.define do
+ factory :sf20_entry, class: Entry do
+ user
+ catalogs ["sf20"]
+ sequence(:date) {|n| (n-1).days.from_now.to_date}
+ responses []
+
+ before(:create) do |entry|
+ # section A
+ entry.responses << build(:response, {catalog: "sf20", name: :general_health, value: 2})
+ # section B
+ entry.responses << build(:response, {catalog: "sf20", name: :limit_vigorous_activity, value: 2})
+ entry.responses << build(:response, {catalog: "sf20", name: :limit_moderate_activity, value: 3})
+ entry.responses << build(:response, {catalog: "sf20", name: :limit_climbing_stairs, value: 2})
+ entry.responses << build(:response, {catalog: "sf20", name: :limit_bending, value: 1})
+ entry.responses << build(:response, {catalog: "sf20", name: :limit_walking, value: 2})
+ entry.responses << build(:response, {catalog: "sf20", name: :limit_basic_activity, value: 1})
+ # section C
+ entry.responses << build(:response, {catalog: "sf20", name: :bodily_pain, value: 6})
+ # section D
+ entry.responses << build(:response, {catalog: "sf20", name: :prevent_working, value: 1})
+ # section E
+ entry.responses << build(:response, {catalog: "sf20", name: :prevent_certain_kinds_work, value: 1})
+ # section F
+ entry.responses << build(:response, {catalog: "sf20", name: :limit_social_activities, value: 1})
+ entry.responses << build(:response, {catalog: "sf20", name: :nervousness, value: 2})
+ entry.responses << build(:response, {catalog: "sf20", name: :calmness, value: 3})
+ entry.responses << build(:response, {catalog: "sf20", name: :downheartedness, value: 4})
+ entry.responses << build(:response, {catalog: "sf20", name: :happiness, value: 5})
+ entry.responses << build(:response, {catalog: "sf20", name: :despair, value: 6})
+ # section G
+ entry.responses << build(:response, {catalog: "sf20", name: :somewhat_ill, value: 5})
+ entry.responses << build(:response, {catalog: "sf20", name: :healthy_as_anybody, value: 1})
+ entry.responses << build(:response, {catalog: "sf20", name: :excellent_health, value: 1})
+ entry.responses << build(:response, {catalog: "sf20", name: :feeling_bad_lately, value: 5})
+
+ # entry.responses << build(:response, {catalog: "rapid3", name: :global_estimate , value: (0..10).step(0.5).to_a.sample})
+
+ Entry.class_eval{ include Sf20Catalog }
+ end
+ after(:create) do |entry|
+ Entry.skip_callback(:save, :after, :enqueue)
+ Entry.perform entry.id, false
+ entry.reload
+ end
+
+ end
+end
+
FactoryGirl.define do
factory :symptom_entry, class: Entry do
user