study_engine 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Rakefile +42 -0
- data/app/assets/images/study_engine/spinner.gif +0 -0
- data/app/assets/images/study_engine/sprite/delete.png +0 -0
- data/app/assets/images/study_engine/sprite/download.png +0 -0
- data/app/assets/images/study_engine/sprite/edit.png +0 -0
- data/app/assets/images/study_engine/sprite/incomplete.png +0 -0
- data/app/assets/images/study_engine/sprite/metrc-sm.png +0 -0
- data/app/assets/images/study_engine/sprite/metrc.png +0 -0
- data/app/assets/images/study_engine/sprite/new.png +0 -0
- data/app/assets/javascripts/study_engine/admin_notes.js.coffee +12 -0
- data/app/assets/javascripts/study_engine/assessment_notifications.js.coffee +36 -0
- data/app/assets/javascripts/study_engine/assessment_timeout.js.coffee +24 -0
- data/app/assets/javascripts/study_engine/disable_buttons_after_click.js.coffee +6 -0
- data/app/assets/javascripts/study_engine/fire_click_event_immediately_on_mobile.js.coffee.erb +10 -0
- data/app/assets/javascripts/study_engine/response_validation.js.coffee +8 -0
- data/app/assets/javascripts/study_engine/restart_assessment_button.js.coffee +3 -0
- data/app/assets/javascripts/study_engine/study_engine.js.coffee +9 -0
- data/app/assets/javascripts/study_engine/study_id_picker.js.coffee +28 -0
- data/app/assets/stylesheets/study_engine.sass +1 -0
- data/app/assets/stylesheets/study_engine/_constant.sass +52 -0
- data/app/assets/stylesheets/study_engine/_mobile.sass +8 -0
- data/app/assets/stylesheets/study_engine/_tablet.sass +36 -0
- data/app/assets/stylesheets/study_engine/application.sass +101 -0
- data/app/assets/stylesheets/study_engine/base/_form.sass +131 -0
- data/app/assets/stylesheets/study_engine/base/_list.sass +38 -0
- data/app/assets/stylesheets/study_engine/base/_tables.sass +33 -0
- data/app/assets/stylesheets/study_engine/base/_text.sass +78 -0
- data/app/assets/stylesheets/study_engine/components/-fields/_login-fields.sass +14 -0
- data/app/assets/stylesheets/study_engine/components/-fields/_start-fields.sass +101 -0
- data/app/assets/stylesheets/study_engine/components/-nav/_global-nav.sass +19 -0
- data/app/assets/stylesheets/study_engine/components/-table/_results-table.sass +75 -0
- data/app/assets/stylesheets/study_engine/components/-table/_users-table.sass +11 -0
- data/app/assets/stylesheets/study_engine/components/_assessment-questions.sass +30 -0
- data/app/assets/stylesheets/study_engine/components/_form-buttons.sass +102 -0
- data/app/assets/stylesheets/study_engine/components/_helpt-text.sass +2 -0
- data/app/assets/stylesheets/study_engine/components/_method-icons.sass +15 -0
- data/app/assets/stylesheets/study_engine/components/_notes-modal.sass +65 -0
- data/app/assets/stylesheets/study_engine/components/_progress-bar.sass +25 -0
- data/app/assets/stylesheets/study_engine/components/_sortable-columns.sass +26 -0
- data/app/assets/stylesheets/study_engine/components/_table-actions.sass +8 -0
- data/app/assets/stylesheets/study_engine/components/_table-buttons.sass +17 -0
- data/app/assets/stylesheets/study_engine/components/_user-privileges.sass +3 -0
- data/app/assets/stylesheets/study_engine/mixins/_actions-table.sass +11 -0
- data/app/assets/stylesheets/study_engine/mixins/_buttons.sass +55 -0
- data/app/assets/stylesheets/study_engine/mixins/_checkbox-columns.sass +50 -0
- data/app/assets/stylesheets/study_engine/mixins/_custom-radios.sass +47 -0
- data/app/assets/stylesheets/study_engine/mixins/_icons.sass +38 -0
- data/app/assets/stylesheets/study_engine/mixins/_notification-text.sass +23 -0
- data/app/controllers/study_engine/admin/application_controller.rb +6 -0
- data/app/controllers/study_engine/admin/assessments_controller.rb +79 -0
- data/app/controllers/study_engine/admin/notes_controller.rb +17 -0
- data/app/controllers/study_engine/application_controller.rb +12 -0
- data/app/controllers/study_engine/assessments_controller.rb +94 -0
- data/app/helpers/study_engine/helpers.rb +15 -0
- data/app/helpers/study_engine/mailer_helpers.rb +11 -0
- data/app/mailers/study_engine/mailer.rb +25 -0
- data/app/models/study_engine/answer.rb +36 -0
- data/app/models/study_engine/assessment.rb +118 -0
- data/app/models/study_engine/begin_assessment_form.rb +110 -0
- data/app/models/study_engine/existing_assessments_form.rb +99 -0
- data/app/models/study_engine/form.rb +59 -0
- data/app/models/study_engine/report.rb +69 -0
- data/app/models/study_engine/result.rb +46 -0
- data/app/models/study_engine/screen_results.rb +54 -0
- data/app/models/study_engine/study_id.rb +47 -0
- data/app/models/study_engine/study_id/bank.rb +38 -0
- data/app/models/study_engine/study_id/sync.rb +52 -0
- data/app/models/study_engine/update_assessments_form.rb +53 -0
- data/app/views/study_engine/_progress_bar.html.slim +4 -0
- data/app/views/study_engine/admin/assessments/_assessment.html.slim +36 -0
- data/app/views/study_engine/admin/assessments/domain_results.csv.csvbuilder +17 -0
- data/app/views/study_engine/admin/assessments/edit.html.slim +68 -0
- data/app/views/study_engine/admin/assessments/index.html.slim +26 -0
- data/app/views/study_engine/admin/assessments/item_results.csv.csvbuilder +24 -0
- data/app/views/study_engine/admin/assessments/new.html.slim +46 -0
- data/app/views/study_engine/admin/assessments/screen_results.csv.csvbuilder +2 -0
- data/app/views/study_engine/assessments/complete.html.slim +25 -0
- data/app/views/study_engine/assessments/create.html.slim +46 -0
- data/app/views/study_engine/assessments/index.html.slim +43 -0
- data/app/views/study_engine/assessments/restart.html.slim +28 -0
- data/app/views/study_engine/assessments/show.html.slim +60 -0
- data/app/views/study_engine/mailer/assessment_recorded_notification.erb +8 -0
- data/app/views/study_engine/mailer/assessment_url_notification.erb +6 -0
- data/config/routes.rb +15 -0
- data/db/migrate/20160422185859_create_tables.rb +52 -0
- data/lib/study_engine.rb +5 -0
- data/lib/study_engine/config.rb +37 -0
- data/lib/study_engine/cucumber.rb +96 -0
- data/lib/study_engine/engine.rb +7 -0
- data/lib/study_engine/version.rb +3 -0
- data/lib/tasks/study_engine_tasks.rake +10 -0
- data/spec/ar_helper.rb +30 -0
- data/spec/models/study_engine/answer_spec.rb +28 -0
- data/spec/models/study_engine/assessment_spec.rb +250 -0
- data/spec/models/study_engine/begin_assessment_form_spec.rb +87 -0
- data/spec/models/study_engine/existing_assessments_form_spec.rb +171 -0
- data/spec/models/study_engine/report_spec.rb +48 -0
- data/spec/models/study_engine/result_spec.rb +102 -0
- data/spec/models/study_engine/screen_results_spec.rb +69 -0
- data/spec/models/study_engine/study_id/bank_spec.rb +72 -0
- data/spec/models/study_engine/study_id/sync_spec.rb +61 -0
- data/spec/models/study_engine/study_id_spec.rb +7 -0
- data/spec/models/study_engine/update_assessments_form_spec.rb +38 -0
- data/spec/rails_helper.rb +50 -0
- data/spec/schema.rb +65 -0
- data/spec/spec_helper.rb +72 -0
- data/spec/support/CATEngine3.exe +0 -0
- data/spec/support/forms/01. Physical Function Bank.xml +1213 -0
- data/spec/support/forms/02. Pain Interference Bank.xml +443 -0
- data/spec/support/forms/03. Anxiety Bank.xml +322 -0
- data/spec/support/forms/04. Depression Bank.xml +311 -0
- data/spec/support/forms/05. Ability to Participate Social bank Version 2.xml +353 -0
- data/spec/support/forms/06. Psychosocial Illness Impact Pos Bank.xml +900 -0
- data/spec/support/forms/07. Applied Cognition Gen Concerns Bank.xml +377 -0
- data/spec/support/forms/08. Satisfaction with Roles and Activities Bank Version 2.xml +443 -0
- data/spec/support/forms/09. Sleep Disturbance Bank.xml +300 -0
- data/spec/support/forms/10. Emotional Support Bank.xml +163 -0
- data/spec/support/forms/A - 07. Psychosocial Illness Impact Pos Bank.xml +471 -0
- data/spec/support/forms/A - 08. Pain interference Bank.xml +443 -0
- data/spec/support/forms/A - 09. Depression Bank.xml +311 -0
- data/spec/support/forms/B - 07. Physical Function Short Form.xml +123 -0
- data/spec/support/forms/B - 08. Physical Function Remainder A.xml +543 -0
- data/spec/support/forms/B - 09. Ability to Participate Social bank Version 2.xml +353 -0
- data/spec/support/forms/C - 07. Physical Function Short Form.xml +123 -0
- data/spec/support/forms/C - 08. Physical Function Remainder B.xml +553 -0
- data/spec/support/forms/C - 09. Anxiety Bank.xml +322 -0
- data/spec/support/parameters/01. Physical Function Bank.xml +682 -0
- data/spec/support/parameters/02. Pain Interference Bank.xml +256 -0
- data/spec/support/parameters/03. Anxiety Bank.xml +190 -0
- data/spec/support/parameters/04. Depression Bank.xml +184 -0
- data/spec/support/parameters/05. Ability to Participate Social bank Version 2.xml +226 -0
- data/spec/support/parameters/06. Psychosocial Illness Impact Pos Bank.xml +211 -0
- data/spec/support/parameters/07. Applied Cognition Gen Concerns Bank.xml +1 -0
- data/spec/support/parameters/08. Satisfaction with Roles and Activities Bank Version 2.xml +1 -0
- data/spec/support/parameters/09. Sleep Disturbance Bank.xml +1 -0
- data/spec/support/parameters/10. Emotional Support Bank.xml +1 -0
- metadata +394 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
require "study_engine/assessment"
|
2
|
+
require "study_engine/config"
|
3
|
+
|
4
|
+
module StudyEngine
|
5
|
+
class ExistingAssessmentsForm
|
6
|
+
include ActiveModel::Validations
|
7
|
+
extend ActiveModel::Naming
|
8
|
+
|
9
|
+
def self.model_name
|
10
|
+
ActiveModel::Name.new(Assessment)
|
11
|
+
end
|
12
|
+
|
13
|
+
validate :format_of_study_id
|
14
|
+
validate :presence_of_some_event
|
15
|
+
validate :presence_of_previous_events
|
16
|
+
|
17
|
+
def initialize attributes = {}
|
18
|
+
self.study_id = StudyID.new
|
19
|
+
self.event_datetimes ||= []
|
20
|
+
attributes.each do |name, value|
|
21
|
+
send("#{name}=", value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_model
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_key
|
30
|
+
end
|
31
|
+
|
32
|
+
def persisted?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_accessor :study_id, :method_applied, :event_datetimes
|
37
|
+
|
38
|
+
delegate :study, :site, :id, to: :study_id, prefix: true
|
39
|
+
delegate :study=, :site=, :id=, to: :study_id, prefix: true
|
40
|
+
|
41
|
+
def method_applied
|
42
|
+
@method_applied || "In-person"
|
43
|
+
end
|
44
|
+
|
45
|
+
def study_options
|
46
|
+
StudyID::Bank.studies
|
47
|
+
end
|
48
|
+
|
49
|
+
def save
|
50
|
+
if valid?
|
51
|
+
base = Assessment.by_study_id(study_id)
|
52
|
+
event_datetimes.each.with_index do |datetime, index|
|
53
|
+
next if datetime.blank?
|
54
|
+
event = StudyEngine.events[index]
|
55
|
+
assessment = base.by_event(event).create!(created_at: datetime, assessment_updated_at: datetime, method_applied: method_applied)
|
56
|
+
assessment.update_column :group_index, nil
|
57
|
+
end
|
58
|
+
true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def format_of_study_id
|
65
|
+
if study_id.present?
|
66
|
+
unless study_id.valid?
|
67
|
+
errors.add :base, "METRC study ID must be in the following format: XXX-XXX-####"
|
68
|
+
end
|
69
|
+
else
|
70
|
+
errors.add :base, "METRC study ID can not be blank. Enter ID in the following format: XXX-XXX-####"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def presence_of_some_event
|
75
|
+
if event_datetimes.all?(&:blank?)
|
76
|
+
events = StudyEngine.events.dup
|
77
|
+
events.map!(&:downcase)
|
78
|
+
events.last.prepend("or ")
|
79
|
+
events = events.join(", ")
|
80
|
+
errors.add :base, "A #{events} timestamp must be present"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def presence_of_previous_events
|
85
|
+
event_datetimes.each.with_index do |datetime, index|
|
86
|
+
next if datetime.blank? || index.nil? || index.zero?
|
87
|
+
event = StudyEngine.events[index]
|
88
|
+
previous_events = StudyEngine.events[0...index]
|
89
|
+
|
90
|
+
previous_events.each.with_index do |previous_event, index|
|
91
|
+
if event_datetimes[index].blank? && Assessment.by_study_id(study_id).by_event(previous_event).none?
|
92
|
+
errors.add :base, "Cannot add a #{event.downcase} assessment when #{previous_event.downcase} assessment does not exist"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "active_support/core_ext/module/delegation"
|
2
|
+
|
3
|
+
module StudyEngine
|
4
|
+
class Form
|
5
|
+
include ActiveModel::Validations
|
6
|
+
extend ActiveModel::Naming
|
7
|
+
|
8
|
+
def self.wraps(model)
|
9
|
+
@wraps = model
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.model_name
|
13
|
+
ActiveModel::Name.new(wrapped_class)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.wrapped_class
|
17
|
+
@wraps.to_s.classify.constantize
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.lookup_ancestors
|
21
|
+
[self]
|
22
|
+
end
|
23
|
+
|
24
|
+
cattr_accessor(:attributes_list) { [] }
|
25
|
+
|
26
|
+
def self.attributes *attributes
|
27
|
+
attributes.each do |attribute|
|
28
|
+
attributes_list << attribute
|
29
|
+
attr_accessor attribute
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.new_from_model model
|
34
|
+
attributes = model.attributes.slice(*attributes_list.map(&:to_s))
|
35
|
+
new attributes
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize attributes = {}, model = self.class.wrapped_class.new
|
39
|
+
@model = model
|
40
|
+
attributes.each do |name, value|
|
41
|
+
send("#{name}=", value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def attributes
|
46
|
+
attributes_list.reduce({}) do |hash, key|
|
47
|
+
hash[key] = send(key)
|
48
|
+
hash
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def save
|
53
|
+
@model.update_attributes(attributes) if valid?
|
54
|
+
end
|
55
|
+
|
56
|
+
delegate :to_model, :to_key, :to_param, :to_partial_path, :persisted?, :new_record?, to: :@model
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "study_engine/config"
|
2
|
+
|
3
|
+
module StudyEngine
|
4
|
+
class Report < Struct.new(:assessments, :field, :direction)
|
5
|
+
def self.event_fields
|
6
|
+
StudyEngine.events.map.with_index { |_, index| :"event_#{index}" }
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.group_class
|
10
|
+
Struct.new(:study_id, *event_fields) do
|
11
|
+
def each(&block)
|
12
|
+
Report.event_fields.map { |field| send(field) }.each(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def max_count
|
16
|
+
Report.event_fields.map { |field| send(field).count }.max
|
17
|
+
end
|
18
|
+
|
19
|
+
def == other
|
20
|
+
values == other.values
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def groups
|
26
|
+
sort build_groups
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_groups
|
30
|
+
assessments.reduce({}) do |groups, assessment|
|
31
|
+
group = groups[assessment.study_id.to_s] || new_empty_group(assessment.study_id)
|
32
|
+
|
33
|
+
field = field_map[assessment.event]
|
34
|
+
|
35
|
+
group.send(field).push assessment
|
36
|
+
group.send(field).sort_by! { |a| a.completed_at ? -1 : 1 }
|
37
|
+
|
38
|
+
groups.merge(assessment.study_id.to_s => group)
|
39
|
+
end.values
|
40
|
+
end
|
41
|
+
|
42
|
+
def sort groups
|
43
|
+
groups.sort do |a, b|
|
44
|
+
a = a.send(field)
|
45
|
+
b = b.send(field)
|
46
|
+
ascending? ? a <=> b : b <=> a
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def ascending?
|
51
|
+
direction == "ASC"
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def field_map
|
57
|
+
StudyEngine.events.inject({}) do |hash, event|
|
58
|
+
index = StudyEngine.events.index(event)
|
59
|
+
hash.merge event => "event_#{index}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def new_empty_group(study_id)
|
64
|
+
empty_groups = StudyEngine.events.map { |_| [] }
|
65
|
+
self.class.group_class.new(study_id, *empty_groups)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "cat_engine/bank"
|
2
|
+
require "cat_engine/engine"
|
3
|
+
|
4
|
+
module StudyEngine
|
5
|
+
class Result < ActiveRecord::Base
|
6
|
+
belongs_to :assessment, touch: true
|
7
|
+
|
8
|
+
has_many :answers
|
9
|
+
|
10
|
+
delegate :current_question, to: :bank
|
11
|
+
|
12
|
+
def bank
|
13
|
+
CatEngine::Bank.new(bank_id, history, assessment.full_history)
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
bank_id.sub(".flat","")
|
18
|
+
end
|
19
|
+
|
20
|
+
def history
|
21
|
+
answers.pluck(:response_id).join(" ")
|
22
|
+
end
|
23
|
+
|
24
|
+
def save_answer answer
|
25
|
+
wrapper do # race condition could bypass validity check
|
26
|
+
if bank.valid_answer?(answer.response_id)
|
27
|
+
answers << answer
|
28
|
+
update_attributes bank.to_h
|
29
|
+
save!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def wrapper
|
37
|
+
# UGH HACK HACK HACK why can't FlatEngine handle being wrapped in a lock?
|
38
|
+
if bank.send(:engine).is_a? CatEngine::Engine
|
39
|
+
with_lock { yield }
|
40
|
+
else
|
41
|
+
yield
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "study_engine/config"
|
2
|
+
|
3
|
+
module StudyEngine
|
4
|
+
class ScreenResults < Struct.new(:groups)
|
5
|
+
def each &block
|
6
|
+
block.call header
|
7
|
+
body block
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def header
|
13
|
+
fields = ["Study ID"]
|
14
|
+
StudyEngine.events.each do |event|
|
15
|
+
fields += ["#{event} incomplete", "#{event} timestamp", "#{event} method", "#{event} coordinator", "#{event} note"]
|
16
|
+
fields += ["#{event} set"] if StudyEngine.groups[event].present?
|
17
|
+
end
|
18
|
+
fields.flatten
|
19
|
+
end
|
20
|
+
|
21
|
+
def body block
|
22
|
+
groups.each do |group|
|
23
|
+
group.max_count.times do |count|
|
24
|
+
col = [group.study_id.to_s]
|
25
|
+
|
26
|
+
col = group.each_with_index.inject(col) do |col, (assessments, index)|
|
27
|
+
col + assessment_segment(assessments[count], index)
|
28
|
+
end
|
29
|
+
|
30
|
+
block.call col
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def assessment_segment assessment, index
|
36
|
+
assessment ? existing_assessment(assessment) : empty_assessment(index)
|
37
|
+
end
|
38
|
+
|
39
|
+
def existing_assessment assessment
|
40
|
+
summary = assessment.incomplete? && !assessment.by_mail? ? assessment.results_summary : ""
|
41
|
+
col = [summary, assessment.assessment_updated_at.to_s, assessment.method_applied, assessment.coordinator.try(:email), assessment.notes.to_s]
|
42
|
+
col << assessment.group_index_name if assessment.group_index
|
43
|
+
col
|
44
|
+
end
|
45
|
+
|
46
|
+
def empty_assessment index
|
47
|
+
col = ["", "", "", "", ""]
|
48
|
+
event = StudyEngine.events[index]
|
49
|
+
col << "" if StudyEngine.groups[event].present?
|
50
|
+
col
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "equalizer"
|
2
|
+
|
3
|
+
module StudyEngine
|
4
|
+
class StudyID
|
5
|
+
def self.load slug
|
6
|
+
return unless slug
|
7
|
+
new slug
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.dump study_id
|
11
|
+
study_id.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
include Equalizer.new(:study, :site, :id)
|
15
|
+
|
16
|
+
attr_accessor :study, :site, :id
|
17
|
+
|
18
|
+
def initialize slug=nil
|
19
|
+
@study, @site, @id = slug.split("-") if slug
|
20
|
+
end
|
21
|
+
|
22
|
+
def study= value
|
23
|
+
@study = value.try(:upcase)
|
24
|
+
end
|
25
|
+
|
26
|
+
def site= value
|
27
|
+
@site = value.try(:upcase)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
[study, site, id].join("-")
|
32
|
+
end
|
33
|
+
|
34
|
+
def <=> other
|
35
|
+
to_s <=> other.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def present?
|
39
|
+
[@study, @site, @id].all?(&:present?)
|
40
|
+
end
|
41
|
+
|
42
|
+
def valid?
|
43
|
+
to_s =~ %r{\A[A-Z]{3}-[A-Z]{3}-[0-9]{4}\Z}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module StudyEngine
|
2
|
+
class StudyID
|
3
|
+
class Bank < ActiveRecord::Base
|
4
|
+
self.table_name = "study_engine_study_ids"
|
5
|
+
|
6
|
+
def self.find_or_create_from_full_study_id study_id
|
7
|
+
study_id_study, study_id_site, study_id_id = study_id.split("-")
|
8
|
+
find_or_create_by study_id_study: study_id_study, study_id_site: study_id_site, study_id_id: study_id_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.studies
|
12
|
+
pluck(:study_id_study).uniq.sort
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.sites
|
16
|
+
pluck(:study_id_site).uniq.sort
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.ids_grouped_by_study_and_site
|
20
|
+
all.reduce(Hash.new([])) do |hash, study_id|
|
21
|
+
hash[study_id.study_id_study + study_id.study_id_site] += [study_id.study_id_id]
|
22
|
+
hash
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.studies_grouped_by_site
|
27
|
+
all.reduce(Hash.new(Set.new)) do |hash, study_id|
|
28
|
+
hash[study_id.study_id_site] += [study_id.study_id_study]
|
29
|
+
hash
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
validates :study_id_study, :study_id_site, format: /\A[A-Z]{3}\Z/
|
34
|
+
validates :study_id_id, format: /\A[0-9]{4}\Z/
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "csv"
|
3
|
+
require "study_engine/config"
|
4
|
+
|
5
|
+
module StudyEngine
|
6
|
+
class StudyID
|
7
|
+
class Sync
|
8
|
+
def self.call
|
9
|
+
new.call
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
raise "Cannot sync with REDCap without setting StudyEngine.redcap_token" if StudyEngine.redcap_token.blank?
|
14
|
+
CSV.parse(response, headers: true) do |row|
|
15
|
+
process row unless row.empty?
|
16
|
+
end
|
17
|
+
StudyID::Bank.where(["updated_at < ?", 30.minutes.ago]).delete_all
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def process row
|
23
|
+
study_id = StudyID::Bank.find_or_create_from_full_study_id(row["study_id"])
|
24
|
+
study_id.touch unless study_id.new_record?
|
25
|
+
end
|
26
|
+
|
27
|
+
def response
|
28
|
+
conn = Faraday.new(url: "https://metrcdata.org")
|
29
|
+
resp = conn.post "/redcap/api/",
|
30
|
+
token: StudyEngine.redcap_token,
|
31
|
+
content: "record",
|
32
|
+
format: "csv"
|
33
|
+
resp.body
|
34
|
+
end
|
35
|
+
|
36
|
+
prepend Module.new {
|
37
|
+
def response
|
38
|
+
retries = 0
|
39
|
+
super
|
40
|
+
rescue Faraday::TimeoutError
|
41
|
+
retries += 1
|
42
|
+
if retries < 5
|
43
|
+
retry
|
44
|
+
else
|
45
|
+
raise
|
46
|
+
end
|
47
|
+
end
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|