study_engine 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|