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.
Files changed (138) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +42 -0
  3. data/app/assets/images/study_engine/spinner.gif +0 -0
  4. data/app/assets/images/study_engine/sprite/delete.png +0 -0
  5. data/app/assets/images/study_engine/sprite/download.png +0 -0
  6. data/app/assets/images/study_engine/sprite/edit.png +0 -0
  7. data/app/assets/images/study_engine/sprite/incomplete.png +0 -0
  8. data/app/assets/images/study_engine/sprite/metrc-sm.png +0 -0
  9. data/app/assets/images/study_engine/sprite/metrc.png +0 -0
  10. data/app/assets/images/study_engine/sprite/new.png +0 -0
  11. data/app/assets/javascripts/study_engine/admin_notes.js.coffee +12 -0
  12. data/app/assets/javascripts/study_engine/assessment_notifications.js.coffee +36 -0
  13. data/app/assets/javascripts/study_engine/assessment_timeout.js.coffee +24 -0
  14. data/app/assets/javascripts/study_engine/disable_buttons_after_click.js.coffee +6 -0
  15. data/app/assets/javascripts/study_engine/fire_click_event_immediately_on_mobile.js.coffee.erb +10 -0
  16. data/app/assets/javascripts/study_engine/response_validation.js.coffee +8 -0
  17. data/app/assets/javascripts/study_engine/restart_assessment_button.js.coffee +3 -0
  18. data/app/assets/javascripts/study_engine/study_engine.js.coffee +9 -0
  19. data/app/assets/javascripts/study_engine/study_id_picker.js.coffee +28 -0
  20. data/app/assets/stylesheets/study_engine.sass +1 -0
  21. data/app/assets/stylesheets/study_engine/_constant.sass +52 -0
  22. data/app/assets/stylesheets/study_engine/_mobile.sass +8 -0
  23. data/app/assets/stylesheets/study_engine/_tablet.sass +36 -0
  24. data/app/assets/stylesheets/study_engine/application.sass +101 -0
  25. data/app/assets/stylesheets/study_engine/base/_form.sass +131 -0
  26. data/app/assets/stylesheets/study_engine/base/_list.sass +38 -0
  27. data/app/assets/stylesheets/study_engine/base/_tables.sass +33 -0
  28. data/app/assets/stylesheets/study_engine/base/_text.sass +78 -0
  29. data/app/assets/stylesheets/study_engine/components/-fields/_login-fields.sass +14 -0
  30. data/app/assets/stylesheets/study_engine/components/-fields/_start-fields.sass +101 -0
  31. data/app/assets/stylesheets/study_engine/components/-nav/_global-nav.sass +19 -0
  32. data/app/assets/stylesheets/study_engine/components/-table/_results-table.sass +75 -0
  33. data/app/assets/stylesheets/study_engine/components/-table/_users-table.sass +11 -0
  34. data/app/assets/stylesheets/study_engine/components/_assessment-questions.sass +30 -0
  35. data/app/assets/stylesheets/study_engine/components/_form-buttons.sass +102 -0
  36. data/app/assets/stylesheets/study_engine/components/_helpt-text.sass +2 -0
  37. data/app/assets/stylesheets/study_engine/components/_method-icons.sass +15 -0
  38. data/app/assets/stylesheets/study_engine/components/_notes-modal.sass +65 -0
  39. data/app/assets/stylesheets/study_engine/components/_progress-bar.sass +25 -0
  40. data/app/assets/stylesheets/study_engine/components/_sortable-columns.sass +26 -0
  41. data/app/assets/stylesheets/study_engine/components/_table-actions.sass +8 -0
  42. data/app/assets/stylesheets/study_engine/components/_table-buttons.sass +17 -0
  43. data/app/assets/stylesheets/study_engine/components/_user-privileges.sass +3 -0
  44. data/app/assets/stylesheets/study_engine/mixins/_actions-table.sass +11 -0
  45. data/app/assets/stylesheets/study_engine/mixins/_buttons.sass +55 -0
  46. data/app/assets/stylesheets/study_engine/mixins/_checkbox-columns.sass +50 -0
  47. data/app/assets/stylesheets/study_engine/mixins/_custom-radios.sass +47 -0
  48. data/app/assets/stylesheets/study_engine/mixins/_icons.sass +38 -0
  49. data/app/assets/stylesheets/study_engine/mixins/_notification-text.sass +23 -0
  50. data/app/controllers/study_engine/admin/application_controller.rb +6 -0
  51. data/app/controllers/study_engine/admin/assessments_controller.rb +79 -0
  52. data/app/controllers/study_engine/admin/notes_controller.rb +17 -0
  53. data/app/controllers/study_engine/application_controller.rb +12 -0
  54. data/app/controllers/study_engine/assessments_controller.rb +94 -0
  55. data/app/helpers/study_engine/helpers.rb +15 -0
  56. data/app/helpers/study_engine/mailer_helpers.rb +11 -0
  57. data/app/mailers/study_engine/mailer.rb +25 -0
  58. data/app/models/study_engine/answer.rb +36 -0
  59. data/app/models/study_engine/assessment.rb +118 -0
  60. data/app/models/study_engine/begin_assessment_form.rb +110 -0
  61. data/app/models/study_engine/existing_assessments_form.rb +99 -0
  62. data/app/models/study_engine/form.rb +59 -0
  63. data/app/models/study_engine/report.rb +69 -0
  64. data/app/models/study_engine/result.rb +46 -0
  65. data/app/models/study_engine/screen_results.rb +54 -0
  66. data/app/models/study_engine/study_id.rb +47 -0
  67. data/app/models/study_engine/study_id/bank.rb +38 -0
  68. data/app/models/study_engine/study_id/sync.rb +52 -0
  69. data/app/models/study_engine/update_assessments_form.rb +53 -0
  70. data/app/views/study_engine/_progress_bar.html.slim +4 -0
  71. data/app/views/study_engine/admin/assessments/_assessment.html.slim +36 -0
  72. data/app/views/study_engine/admin/assessments/domain_results.csv.csvbuilder +17 -0
  73. data/app/views/study_engine/admin/assessments/edit.html.slim +68 -0
  74. data/app/views/study_engine/admin/assessments/index.html.slim +26 -0
  75. data/app/views/study_engine/admin/assessments/item_results.csv.csvbuilder +24 -0
  76. data/app/views/study_engine/admin/assessments/new.html.slim +46 -0
  77. data/app/views/study_engine/admin/assessments/screen_results.csv.csvbuilder +2 -0
  78. data/app/views/study_engine/assessments/complete.html.slim +25 -0
  79. data/app/views/study_engine/assessments/create.html.slim +46 -0
  80. data/app/views/study_engine/assessments/index.html.slim +43 -0
  81. data/app/views/study_engine/assessments/restart.html.slim +28 -0
  82. data/app/views/study_engine/assessments/show.html.slim +60 -0
  83. data/app/views/study_engine/mailer/assessment_recorded_notification.erb +8 -0
  84. data/app/views/study_engine/mailer/assessment_url_notification.erb +6 -0
  85. data/config/routes.rb +15 -0
  86. data/db/migrate/20160422185859_create_tables.rb +52 -0
  87. data/lib/study_engine.rb +5 -0
  88. data/lib/study_engine/config.rb +37 -0
  89. data/lib/study_engine/cucumber.rb +96 -0
  90. data/lib/study_engine/engine.rb +7 -0
  91. data/lib/study_engine/version.rb +3 -0
  92. data/lib/tasks/study_engine_tasks.rake +10 -0
  93. data/spec/ar_helper.rb +30 -0
  94. data/spec/models/study_engine/answer_spec.rb +28 -0
  95. data/spec/models/study_engine/assessment_spec.rb +250 -0
  96. data/spec/models/study_engine/begin_assessment_form_spec.rb +87 -0
  97. data/spec/models/study_engine/existing_assessments_form_spec.rb +171 -0
  98. data/spec/models/study_engine/report_spec.rb +48 -0
  99. data/spec/models/study_engine/result_spec.rb +102 -0
  100. data/spec/models/study_engine/screen_results_spec.rb +69 -0
  101. data/spec/models/study_engine/study_id/bank_spec.rb +72 -0
  102. data/spec/models/study_engine/study_id/sync_spec.rb +61 -0
  103. data/spec/models/study_engine/study_id_spec.rb +7 -0
  104. data/spec/models/study_engine/update_assessments_form_spec.rb +38 -0
  105. data/spec/rails_helper.rb +50 -0
  106. data/spec/schema.rb +65 -0
  107. data/spec/spec_helper.rb +72 -0
  108. data/spec/support/CATEngine3.exe +0 -0
  109. data/spec/support/forms/01. Physical Function Bank.xml +1213 -0
  110. data/spec/support/forms/02. Pain Interference Bank.xml +443 -0
  111. data/spec/support/forms/03. Anxiety Bank.xml +322 -0
  112. data/spec/support/forms/04. Depression Bank.xml +311 -0
  113. data/spec/support/forms/05. Ability to Participate Social bank Version 2.xml +353 -0
  114. data/spec/support/forms/06. Psychosocial Illness Impact Pos Bank.xml +900 -0
  115. data/spec/support/forms/07. Applied Cognition Gen Concerns Bank.xml +377 -0
  116. data/spec/support/forms/08. Satisfaction with Roles and Activities Bank Version 2.xml +443 -0
  117. data/spec/support/forms/09. Sleep Disturbance Bank.xml +300 -0
  118. data/spec/support/forms/10. Emotional Support Bank.xml +163 -0
  119. data/spec/support/forms/A - 07. Psychosocial Illness Impact Pos Bank.xml +471 -0
  120. data/spec/support/forms/A - 08. Pain interference Bank.xml +443 -0
  121. data/spec/support/forms/A - 09. Depression Bank.xml +311 -0
  122. data/spec/support/forms/B - 07. Physical Function Short Form.xml +123 -0
  123. data/spec/support/forms/B - 08. Physical Function Remainder A.xml +543 -0
  124. data/spec/support/forms/B - 09. Ability to Participate Social bank Version 2.xml +353 -0
  125. data/spec/support/forms/C - 07. Physical Function Short Form.xml +123 -0
  126. data/spec/support/forms/C - 08. Physical Function Remainder B.xml +553 -0
  127. data/spec/support/forms/C - 09. Anxiety Bank.xml +322 -0
  128. data/spec/support/parameters/01. Physical Function Bank.xml +682 -0
  129. data/spec/support/parameters/02. Pain Interference Bank.xml +256 -0
  130. data/spec/support/parameters/03. Anxiety Bank.xml +190 -0
  131. data/spec/support/parameters/04. Depression Bank.xml +184 -0
  132. data/spec/support/parameters/05. Ability to Participate Social bank Version 2.xml +226 -0
  133. data/spec/support/parameters/06. Psychosocial Illness Impact Pos Bank.xml +211 -0
  134. data/spec/support/parameters/07. Applied Cognition Gen Concerns Bank.xml +1 -0
  135. data/spec/support/parameters/08. Satisfaction with Roles and Activities Bank Version 2.xml +1 -0
  136. data/spec/support/parameters/09. Sleep Disturbance Bank.xml +1 -0
  137. data/spec/support/parameters/10. Emotional Support Bank.xml +1 -0
  138. metadata +394 -0
@@ -0,0 +1,17 @@
1
+ module StudyEngine
2
+ class Admin::NotesController < Admin::ApplicationController
3
+ before_action :require_admin
4
+
5
+ def update
6
+ assessment.update_column :notes, params[:notes]
7
+ render text: assessment.notes?
8
+ end
9
+
10
+ private
11
+
12
+ def assessment
13
+ Assessment.find(params[:assessment_id])
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,12 @@
1
+ class StudyEngine::ApplicationController < ::ApplicationController
2
+ helper Helpers
3
+
4
+ protect_from_forgery with: :exception
5
+
6
+ def self.expose *methods
7
+ methods.each do |method|
8
+ attr_reader method
9
+ helper_method method
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,94 @@
1
+ require "study_engine/config"
2
+ require "cat_engine/question"
3
+
4
+ module StudyEngine
5
+ class AssessmentsController < ::StudyEngine::ApplicationController
6
+ expose :assessment, :welcome_content, :events, :enabled_application_methods
7
+
8
+ def index
9
+ @welcome_content = StudyEngine.welcome_content
10
+ @assessment = BeginAssessmentForm.new
11
+ assessment.coordinator = current_user
12
+ end
13
+
14
+ def create
15
+ @assessment = BeginAssessmentForm.new(params[:assessment])
16
+ assessment.coordinator = current_user
17
+ if assessment.save
18
+ if assessment.by_url?
19
+ flash.now.notice = "An assessment has been created."
20
+ ::StudyEngine::Mailer.assessment_url_notification(current_user.email, assessment).deliver_now
21
+ render :create
22
+ elsif assessment.by_mail?
23
+ flash.now.notice = "An assessment has been created."
24
+ render :create
25
+ else
26
+ redirect_to assessment
27
+ end
28
+ elsif assessment.restartable?
29
+ flash.now[:alert] = "Partial assessment data already exists for this ID number and event. Would you like to resume or restart?"
30
+ render :restart
31
+ else
32
+ flash.now[:alert] = assessment.errors.full_messages.join(", ")
33
+ render :index
34
+ end
35
+ end
36
+
37
+ def show
38
+ if assessment.complete?
39
+ if assessment.completed_at.nil?
40
+ assessment.touch :completed_at
41
+ send_notification
42
+ end
43
+ render :complete
44
+ else
45
+ render :show
46
+ end
47
+ end
48
+
49
+ def edit
50
+ redirect_to assessment
51
+ end
52
+
53
+ def update
54
+ assessment.save_answer Answer.new(question: CatEngine::Question.new(params[:cat_engine_question]))
55
+ redirect_to assessment
56
+ end
57
+
58
+ def timeout
59
+ @assessment = BeginAssessmentForm.new_from_model(assessment)
60
+ flash.now[:alert] = "Assessment timed out due to inactivity. Would you like to resume or restart?"
61
+ render :restart
62
+ end
63
+
64
+ def notification
65
+ send_notification
66
+ head :ok
67
+ end
68
+
69
+ private
70
+
71
+ def send_notification
72
+ if email = assessment.coordinator_email
73
+ ::StudyEngine::Mailer.assessment_recorded_notification(email, assessment).deliver_now
74
+ end
75
+ end
76
+
77
+ def assessment
78
+ @assessment ||= Assessment.find(params[:id])
79
+ end
80
+
81
+ def current_user_required?
82
+ return true unless %w[show update notification].include?(params[:action]) && assessment.by_url?
83
+ end
84
+
85
+ def events
86
+ StudyEngine.events
87
+ end
88
+
89
+ def enabled_application_methods
90
+ StudyEngine.enabled_application_methods
91
+ end
92
+ end
93
+ end
94
+
@@ -0,0 +1,15 @@
1
+ module StudyEngine
2
+ module Helpers
3
+ def progress_bar percent
4
+ render partial: "study_engine/progress_bar", locals: { percent: percent }
5
+ end
6
+
7
+ def sortable_link_url report, field
8
+ "?field=#{field}&direction=#{(report.field == field && report.direction == "ASC") ? "DESC" : "ASC"}"
9
+ end
10
+
11
+ def sortable_link_class report, field
12
+ "current-#{report.direction.downcase}" if report.field == field
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module StudyEngine
2
+ module MailerHelpers
3
+ def domains_and_group_summary assessment
4
+ finished = assessment.results.select(&:finished?).count
5
+ total = assessment.results.count
6
+ domains = [finished, total].join("/")
7
+ [domains, assessment.group_index_name].join(" ").strip
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,25 @@
1
+ require "study_engine/config"
2
+
3
+ module StudyEngine
4
+ class Mailer < ActionMailer::Base
5
+ helper MailerHelpers
6
+
7
+ default content_type: "text/html"
8
+
9
+ def assessment_url_notification email, assessment
10
+ @assessment = assessment
11
+ subject = "URL for #{assessment.study_id} #{assessment.event}"
12
+ mail to: email,
13
+ subject: subject,
14
+ content_type: "text/plain"
15
+ end
16
+
17
+ def assessment_recorded_notification email, assessment
18
+ @study_name = StudyEngine.study_name
19
+ @assessment = assessment
20
+ subject = "#{assessment.study_id} #{assessment.event} recorded"
21
+ mail to: email, subject: subject, content_type: "text/plain", cc: StudyEngine.notification_email
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,36 @@
1
+ require "cat_engine/response"
2
+ require "cat_engine/question"
3
+
4
+ module StudyEngine
5
+ class Answer < ActiveRecord::Base
6
+ belongs_to :result
7
+
8
+ def question
9
+ CatEngine::Question.new(id: question_id, promis_id: question_promis_id, context: question_context, title: question_title, responses: [])
10
+ end
11
+
12
+ def question= value
13
+ self.response = value.selected_response
14
+ value.each_pair do |key, value|
15
+ if key == :response_before
16
+ self.response_before = value
17
+ else
18
+ send :"question_#{key}=", value
19
+ end
20
+ end
21
+ end
22
+
23
+ def response
24
+ CatEngine::Response.new(id: response_id, promis_id: response_promis_id, before: response_before, title: response_title)
25
+ end
26
+
27
+ def response= value
28
+ value.each_pair do |key, value|
29
+ send :"response_#{key}=", value
30
+ end
31
+ end
32
+
33
+ attr_accessor :question_responses
34
+ end
35
+ end
36
+
@@ -0,0 +1,118 @@
1
+ require "study_engine/config"
2
+ require "study_engine/study_id"
3
+
4
+ module StudyEngine
5
+ class Assessment < ActiveRecord::Base
6
+ cattr_accessor(:timeout_interval) { 10.minutes }
7
+
8
+ default_scope -> { includes(:persisted_results) }
9
+
10
+ def timestamp_attributes_for_create
11
+ super + [:assessment_updated_at]
12
+ end
13
+
14
+ def self.by_event event
15
+ where(event: event)
16
+ end
17
+
18
+ def self.by_study_id study_id
19
+ return none unless study_id
20
+ where(study_id: study_id)
21
+ end
22
+
23
+ def self.by_site site
24
+ where("study_id RLIKE '...-#{site}-....'")
25
+ end
26
+
27
+ has_many :persisted_results, class_name: "Result", dependent: :destroy
28
+
29
+ before_create :assign_group_index
30
+
31
+ belongs_to :coordinator, class_name: "User"
32
+
33
+ def coordinator_email
34
+ coordinator.try(:email)
35
+ end
36
+
37
+ def restarted_assessment
38
+ Assessment.by_study_id(study_id).where(event: event).where.not(id: id).last
39
+ end
40
+
41
+ def stream_id
42
+ StudyID.new("STR-#{study_id.site}-#{1000 + id}") unless new_record?
43
+ end
44
+
45
+ def notes?
46
+ notes.present?
47
+ end
48
+
49
+ serialize :study_id, StudyID
50
+
51
+ def results
52
+ @results ||= begin
53
+ bank_ids = StudyEngine.bank_finder.call(self)
54
+ return persisted_results if persisted_results.length == bank_ids.length
55
+ bank_ids.map do |bank_id|
56
+ persisted_results.find_or_initialize_by(bank_id: bank_id)
57
+ end
58
+ end
59
+ end
60
+
61
+ def current_result
62
+ results.find { |result| !result.finished? }
63
+ end
64
+
65
+ def percent_complete
66
+ (results.select(&:finished?).count / results.count.to_f * 100).round
67
+ end
68
+
69
+ delegate :current_question, to: :current_result
70
+
71
+ def save_answer answer
72
+ current_result.save_answer answer
73
+ save!
74
+ touch :assessment_updated_at
75
+ end
76
+
77
+ def complete?
78
+ current_result.nil?
79
+ end
80
+
81
+ def incomplete?
82
+ !complete?
83
+ end
84
+
85
+ def results_summary
86
+ "#{results.select(&:finished?).count}/#{results.count}"
87
+ end
88
+
89
+ def full_history
90
+ persisted_results.flat_map(&:answers).map(&:question_id)
91
+ end
92
+
93
+ def by_url?
94
+ method_applied == "By URL"
95
+ end
96
+
97
+ def by_mail?
98
+ method_applied == "By mail"
99
+ end
100
+
101
+ def group_index_name
102
+ return "" if group_index.nil?
103
+ event_groups[group_index]
104
+ end
105
+
106
+ private
107
+
108
+ def assign_group_index
109
+ return unless event_groups.any?
110
+ self.group_index = restarted_assessment.try(:group_index) || self.class.by_event(event).count % event_groups.length
111
+ end
112
+
113
+ def event_groups
114
+ StudyEngine.groups[event] || []
115
+ end
116
+ end
117
+ end
118
+
@@ -0,0 +1,110 @@
1
+ require "study_engine/form"
2
+ require "study_engine/assessment"
3
+ require "study_engine/config"
4
+
5
+ module StudyEngine
6
+ class BeginAssessmentForm < Form
7
+ wraps "study_engine/assessment"
8
+
9
+ attributes :study_id, :event, :method_applied, :coordinator
10
+
11
+ attr_accessor :restart
12
+
13
+ delegate :study, :site, :id, to: :study_id, prefix: true
14
+ delegate :study=, :site=, :id=, to: :study_id, prefix: true
15
+
16
+ validate :presence_of_event
17
+ validate :format_of_study_id
18
+ validate :uniqueness_of_study_id_and_event, unless: :restart
19
+ validate :presence_of_previous_events
20
+
21
+ def study_id_study
22
+ study_options.one? ? study_options.first : study_id.study
23
+ end
24
+
25
+ def study_id_site
26
+ site_options.one? ? site_options.first : study_id.site
27
+ end
28
+
29
+ def study_id_id
30
+ id_options.one? ? id_options.first : study_id.id
31
+ end
32
+
33
+ def study_id
34
+ @study_id ||= StudyID.new
35
+ end
36
+
37
+ def method_applied
38
+ @method_applied || "In-person"
39
+ end
40
+
41
+ def existing_assessment
42
+ if study_id and new_record?
43
+ @existing_assessment ||= Assessment.by_study_id(study_id).where(event: event).last
44
+ end
45
+ end
46
+
47
+ def restartable?
48
+ existing_assessment.try(:incomplete?)
49
+ end
50
+
51
+ def study_options
52
+ StudyID::Bank.studies_grouped_by_site.fetch(coordinator.try(:site_id)) { StudyID::Bank.studies }
53
+ end
54
+
55
+ def site_options
56
+ coordinator.try(:site_id).present? ? [coordinator.site_id] : StudyID::Bank.sites
57
+ end
58
+
59
+ def id_options
60
+ ids_grouped_by_study_and_site[study_id_study.to_s + study_id_site.to_s]
61
+ end
62
+
63
+ def ids_grouped_by_study_and_site
64
+ StudyID::Bank.ids_grouped_by_study_and_site
65
+ end
66
+
67
+ delegate :by_url?, :by_mail?, :group_index_name, :created_at, :assessment_updated_at, to: :@model
68
+
69
+ private
70
+
71
+ def format_of_study_id
72
+ if study_id.present?
73
+ unless study_id.valid?
74
+ errors.add :base, "METRC study ID must be in the following format: XXX-XXX-####"
75
+ end
76
+ else
77
+ errors.add :base, "METRC study ID can not be blank. Enter ID in the following format: XXX-XXX-####"
78
+ end
79
+ end
80
+
81
+ def presence_of_event
82
+ if event.blank?
83
+ events = StudyEngine.events.dup
84
+ events.map!(&:titleize)
85
+ events.last.prepend("or ")
86
+ events = events.join(", ")
87
+ errors.add :base, "Study event can not be blank. Choose #{events} assessment."
88
+ end
89
+ end
90
+
91
+ def uniqueness_of_study_id_and_event
92
+ if existing_assessment
93
+ errors.add :base, "Assessment data has already been collected for this ID number and event. Please check these fields to confirm you are beginning an assessment for the correct ID and event."
94
+ end
95
+ end
96
+
97
+ def presence_of_previous_events
98
+ index = StudyEngine.events.index(event)
99
+ return if index.nil? || index.zero?
100
+ previous_events = StudyEngine.events[0...index]
101
+
102
+ previous_events.each do |previous_event|
103
+ if Assessment.by_study_id(study_id).by_event(previous_event).none?
104
+ errors.add :base, "#{previous_event.capitalize} assessment must already exist to administer #{event} assessment"
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+